David G. Andersen
11/15/2004 7:45:00 PM
On Mon, Nov 15, 2004 at 08:48:24AM +0900, Dennis Ranke scribed:
> Here is a small update to my solution. A simple optimization speeds up
> the solver by a factor of nearly two. Now I'm down to 5 seconds for the
> worst case on this machine :)
Thanks for the inspiration to improve my code a little more. :-)
This version uses a search trie to perform duplicate elimination,
and steals Dennis's point about not needing to worry about negative
numbers (since there's also a "minus" operator.. can't believe I forgot
that the first time). The code is a little cleaner, and the memory
utilization is down to about 2.5 megabytes for the memoized version,
since it now knows that if you test "25 5 3" you don't need to test "25 5"
or "25".
the search trie addition is kind of cute. It's a nice demonstration
of how easy it can be to implement some cool data structures in Ruby. :)
Runtime is about 2.7 seconds using -m for an unsolvable case,
and 2 seconds for the "hard" solvable case of 926 75 2 8 5 10 10 that
someone posted earlier. Without -m, it takes about 12 seconds / 5
seconds, respectively, but there's no reason not to use memoization since
it consumes so little memory now.
#!/usr/local/bin/ruby
# The exit from the processing loop is a little ugly. Would be
# better to cascade the return values, but that required more
# tests. ;-)
#
# Use with "-m" to memoize parts of the solution space and avoid
# duplicate configurations. Requires about 14 megs of ram; runs
# about 10x faster.
raise "usage: quiz.rb [-m] [target] [source1] [source2] ...\n" if ARGV.length < 2
$lots_of_memory = ARGV.delete("-m")
target, *source = ARGV.map { |a| a.to_i }
class TreeMap
# Quick and dirty search trie for duplicate detection / elimination
def initialize()
@root = Hash.new
end
def test_and_add(arr)
cur = @root
found = true
arrs = arr.sort
while (head = arrs.pop)
found = false unless cur.has_key?(head)
cur = cur[head] ||= Hash.new
end
return found
end
end
$tm = TreeMap.new if $lots_of_memory
$closest_diff = target
$closest_stack = nil
$itercount = 0
def fs(stack, target, source)
$itercount += 1
recent = source[-1]
raise "#{stack[-1]}" if (recent == target)
return false if ($lots_of_memory && $tm.test_and_add(source))
if (recent - target).abs < $closest_diff
$closest_diff = (recent - target).abs
$closest_stack = stack[-1]
end
return false if (source.length == 1)
i = j = ns = nt = ival = istack = jval = jstack = myid = 0
observed = Hash.new
(0...source.length).each do |i|
(i+1...source.length).each do |j|
ns = source[0...i] + source[i+1...j] + source[j+1..-1]
nt = stack[0...i] + stack[i+1...j] + stack[j+1..-1]
i, j = j, i if (source[i] < source[j])
ival, istack = source[i], stack[i]
jval, jstack = source[j], stack[j]
# Linear space duplicate suppression is cheap; use always
myid = "#{ival}-#{jval}"
next if (observed.has_key?(myid))
observed[myid] = true
fs(nt + ["(#{istack} + #{jstack})"], target, ns + [ival + jval])
fs(nt + ["(#{istack} - #{jstack})"], target, ns + [ival - jval]) unless ival==jval
if (jval > 1)
if (ival != jval && 0 == (ival % jval))
fs(nt + ["(#{istack} / #{jstack})"], target, ns + [ival / jval])
end
fs(nt + ["(#{istack} * #{jstack})"], target, ns + [ival * jval])
end
end
end
end
begin
raise "Source contains target." if source.include? target
fs(source.dup, target, source)
p $closest_stack
rescue => err
print "Done: #{err}\n"
ensure
print "Itercount: #{$itercount}\n"
end