[QUIZ] Countdown (#7)

D

Dennis Ranke

Brian said:
Hello Group,

This was quite an interesting quiz. I tried different solutions of varying
complexity, but it turned out that the simplest approach worked best.

That's a nice solution, but reading through the code I have one question
regarding the memoized version:
def Integral.each_term_over(source, memo = {}, &block)
return memo[source] if memo[source]

Shouldn't each_term_over call the block for each term when the terms for
this set of sources was already generated before as well? Now it just
returns the terms but each_term_over itself just ignore the return
value. But maybe I just didn't understand the code correctly, after all,
it appears to work well (although using too much memory on my machine ;)
 
B

Brian Schröder

Brian said:
Hello Group,

This was quite an interesting quiz. I tried different solutions of varying
complexity, but it turned out that the simplest approach worked best.

That's a nice solution, but reading through the code I have one question
regarding the memoized version:
def Integral.each_term_over(source, memo = {}, &block)
return memo[source] if memo[source]

Shouldn't each_term_over call the block for each term when the terms for
this set of sources was already generated before as well? Now it just
returns the terms but each_term_over itself just ignore the return
value. But maybe I just didn't understand the code correctly, after all,
it appears to work well (although using too much memory on my machine ;)

each_term_over calls the block once for each unique term it generates. When a set of terms is already memoized, the block has been called for each of the terms in the set, so it is not neccessary to call it again.

This additionally speeds up the process.

Regards,

Brian
 
D

Dennis Ranke

Brian said:
each_term_over calls the block once for each unique term it generates.
When a set of terms is already memoized, the block has been called for
each of the terms in the set, so it is not neccessary to call it again.

This additionally speeds up the process.

Oh yes, I misread the source, sorry. Of course each_term_over itself
uses the returnvalue, I didn't notice the each the first time I read the
source and thought the block was called by the recursive function.

Regards, Dennis
 
D

David G. Andersen

I recursively generate each possible term, and evaluate it. In
my recursive generation process, each subterm is generated multiple
times, so I used optional memoization to speed up the search. An
exhaustive search takes around 300s without and 110s with memoization,
but memoization uses a lot of ram.

Sounds like my solution, which is appended below, is
pretty similar. The only cleverness in it is doing things
pairwise to easily eliminate commutativity when possible. The
code is a little opaque - sorry. :) Typically finds a solution within
12 seconds if you use the memoization, and 120 seconds if you
don't. I have another version that uses less memory by more
aggressively pruning the space, but because it does more tests,
it actually ends up being a little slower. Oops.

Usage: quiz.rb [-m] [target] [val1] [val2] ... [valn]

I don't suggest running it with more than 6 sub-values, however,
unless you're really patient. the runtime is roughly:

6^5 * 6*5 * 5*4 * 4*3 * 3*2 * 2*1

It's not elegant, but it works. I do apologize profusely
for my terribly lame use of "raise" to exit early from the
processing loop. But it saved a lot of typing. :)

-Dave

#!/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 }

$closest_diff = target
$closest_stack = nil
$nodupes = Hash.new if $lots_of_memory
$itercount = 0

def fs(stack, target, source)
$itercount += 1
recent = source[-1]
raise "#{stack[-1]}" if (recent == target)
return false if recent == 0
if ($lots_of_memory)
wholeid = source.sort.join(" ")
return false if ($nodupes.has_key?(wholeid))
$nodupes[wholeid] = true
end
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|
ival, istack = source, stack
(i+1...source.length).each do |j|
jval, jstack = source[j], stack[j]
# Linear space duplicate suppression is cheap; use always
myid = (ival < jval) ? "#{ival}-#{jval}" : "#{jval}-#{ival}"
next if (observed[myid])
observed[myid] = true
ns = source[0...i] + source[i+1...j] + source[j+1..-1]
nt = stack[0...i] + stack[i+1...j] + stack[j+1..-1]
fs(nt + ["(#{istack} + #{jstack})"], target, ns + [ival + jval])
if (ival != jval)
fs(nt + ["(#{istack} - #{jstack})"], target, ns + [ival - jval])
fs(nt + ["(#{jstack} - #{istack})"], target, ns + [jval - ival])
end
if (ival != 1 && jval != 1)
fs(nt + ["(#{istack} * #{jstack})"], target, ns + [ival * jval])
if (0 == (ival % jval))
fs(nt + ["(#{istack} / #{jstack})"], target, ns + [ival / jval])
end
if (0 == (jval % ival))
fs(nt + ["(#{jstack} / #{istack})"], target, ns + [jval / ival])
end
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
 
J

James Edward Gray II

# The exit from the processing loop is a little ugly. Would be
# better to cascade the return values, but that required more
# tests. ;-)

Just FYI, Ruby's catch/throw is probably a good choice here. It should
work just like your exception throw, but is probably better style.

Nice solution.

James Edward Gray II
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,000
Messages
2,570,252
Members
46,848
Latest member
CristineKo

Latest Threads

Top