Those tests weren’t necessarily representative of the “average” case. What is the
“average” case anyway? Why should it be a returns-true case? There are more
combinations which should return false then return true given the array/matrix sizes,
aren’t there?. If you take (non-trivial) best and worst cases, you see actually quite
different patterns. On the one hand:
···
Using SlowFailArgs, nitems = 7
dnaseby (10 iterations) 0.055341 (0.0055341 per iter)
george (10 iterations) 1.627122 (0.1627122 per iter)
Using SlowFailArgs, nitems = 7
dnaseby (10 iterations) 0.051418 (0.0051418 per iter)
george (10 iterations) 1.614257 (0.1614257 per iter)
Using SlowFailArgs, nitems = 7
dnaseby (10 iterations) 0.050687 (0.0050687 per iter)
george (10 iterations) 1.639815 (0.1639815 per iter)
… and on the other:
Using QuickFailArgs, nitems = 300
dnaseby (10 iterations) 3.198134 (0.3198134 per iter)
george (10 iterations) 0.658672 (0.0658672 per iter)
Using QuickFailArgs, nitems = 300
dnaseby (10 iterations) 3.216936 (0.3216936 per iter)
george (10 iterations) 0.659457 (0.0659457 per iter)
Using QuickFailArgs, nitems = 300
dnaseby (10 iterations) 3.227569 (0.3227569 per iter)
george (10 iterations) 0.662572 (0.0662572 per iter)
(The script used to generate these is below.)
Yes, mine is choking on a mere 7 elements in the worst case (and starts turning purple at
double figures), but at least it’s correct. After looking into dnaseby’s, I found he cut
one too many corners — this should be false, right?:
one_in_each([1,2,3,4,5], [[1,2,3,4,5],[1,2,3,4,5],[1,2,0,0,0],[1,2,0,0,0],[1,2,0,0,0]])
To correct this, his worst-case complexity must increase dramatically, I think.
The test script (Bill’s slightly modified; I don’t have the unit test stuff installed):
#!/usr/bin/ruby
The algorithms
def one_in_each(items,rows)
return false if items.length != rows.length
return true if rows.empty?
item_matches =
items.each_with_index do |item, index|
item_matches << find_matches(item, rows)
return false if item_matches[index].empty?
end
taken_indices =
rows.length.times do
item_matches.each_with_index do |matches, index|
if matches.length == 1
return false if taken_indices.include? matches[0]
taken_indices << matches[0]
item_matches.delete_at(index)
else
matches.delete_if {|i| taken_indices.include?(i)}
end
end
return true if taken_indices.length == rows.length
end
return item_matches.flatten.uniq.length + taken_indices.length == rows.length
end
def find_matches(item, rows)
indexArr =
rows.each_with_index do |row, index|
indexArr << index if row.detect {|toMatch| toMatch == item }
end
return indexArr
end
def one_in_each2
i,r;i==||r.find{|s|s.include?(i[0])&&one_in_each2(i[1..-1],r.reject{|e|s.id==e.id})}!=nil;end
Arg generators – “Quick”/“Slow” correspond to one_in_each2
QuickFailArgs = proc{|n|[Array.new(n).map!{0}, Array.new(n).map!{Array.new(n).map!{1}}]}
SlowFailArgs = proc {|n|
if n == 0
[,]
else
i = Array.new(n,0)
r = Array.new(n).map{e = Array.new(n,1); e[-1] = 0; e}
r[-1][-1] = 1
[i,r]
end
}
QuickPassArgs = proc {|n|
[(1..n).to_a, (1..n).map{|i|Array.new(n,i)}]
}
SlowPassArgs = proc {|n|
[(1..n).to_a, (0…n).to_a.map!{|i| (1..(n-i)).to_a + Array.new(i,0)}]
}
BK’s test (modified)
class Array # from http://www.rubygarden.org/ruby?OneLiners
def shuffle!
each_index {|j| i = rand(size-j); self[j], self[j+i] = self[j+i], self[j]}
end
end
#require ‘test/unit’
#class TestMe < Test::Unit::TestCase
def test_big
puts “\n------------------”
srand 314159
nitems = 300 # if increased to just 70, the slow solutions take "forever" !
big_items = [*1..nitems]
big_items.shuffle!
big_rows = []
big_items.each_with_index do |elm,idx|
big_rows[idx] = []
5.times {|i| big_rows[idx] << rand(1000) }
big_rows[idx] << elm
5.times {|i| big_rows[idx] << rand(1000) }
end
big_items.shuffle!
argspec = 'Random pass'
# Uncomment one.
#big_items, big_rows = QuickPassArgs.call(nitems); argspec = 'QuickPassArgs'
#big_items, big_rows = SlowPassArgs.call(nitems); argspec = 'SlowPassArgs'
#big_items, big_rows = QuickFailArgs.call(nitems); argspec = 'QuickFailArgs'
#big_items, big_rows = SlowFailArgs.call(nitems); argspec = 'SlowFailArgs'
puts "Using #{argspec}, nitems = #{nitems}"
niter = 10
#auths = %w(dblack dnaseby transami avi bwk george)
auths = %w(dnaseby george)
auths.each do |auth|
eval <<-EOE
print " #{auth} "
t1 = Time.now
#niter.times { assert( #{auth}_one_in_each(big_items, big_rows) ) }
niter.times {
case auth
when 'dnaseby'
one_in_each(big_items, big_rows)
when 'george'
one_in_each2(big_items, big_rows)
end
}
t2 = Time.now
puts "(\#{niter} iterations) \#{(t2-t1)} (\#{(t2-t1)/niter.to_f} per iter)"
EOE
end
end
#end
3.times{test_big}