For those interested here is my first solution (you know, the one taking
up to an hour ;)
It generates the expressions as trees and iterates through all possible
trees using recursion. Every tree is then evaluated and the result
compared to the target.
class Solver
class Node
def initialize(parent, depth)
@value = nil
@parent = parent
@right_filled = false
if depth > 1
@left = Node.new(self, depth - 1)
@right = Node.new(self, depth - 1)
end
end
def down(values, num_open)
# first try single values
used = []
touse = values.dup
@value = nil
until touse.empty?
used << @value if @value
@value = touse.shift
@parent.up(used + touse, num_open)
end
# now try subexpression if enough values left
return if values.size < num_open + 2
@value = nil
[:+, :-, :*, :/].each do |@op|
@left.down(values, num_open + 1)
end
end
def up(values, num_open)
if @right_filled
@parent.up(values, num_open)
return
end
@right_filled = true
@right.down(values, num_open - 1)
@right_filled = false
end
def evaluate
return @value.to_f if @value
@left.evaluate.send(@op, @right.evaluate)
end
def to_s
return @value.to_s if @value
"(#@left #@op #@right)"
end
end
def initialize(sources, target)
@tree = Node.new(self, sources.size)
@target = target
@sources = sources
@best_result = (target * 1000).abs
end
def run
@tree.down(@sources, 0)
end
def up(values, num_open)
# return unless values.empty? # only allow solutions using all values
result = @tree.evaluate
return if result.nan? || @best_result <= (@target - result).abs
@best_result = (@target - result).abs
printf "%s = %f\n", @tree, result
exit if @best_result == 0
end
end
if ARGV.size < 3
puts "Usage: ruby countdown.rb <target> <source1> <source2> ..."
exit
end
solver = Solver.new(ARGV[1..-1].map {|v| v.to_i}, ARGV[0].to_i)
solver.run