From: Chris Parker Date: 2006-01-02T11:22:57+09:00 Subject: Re: Numeric Maze (#60) Hey everyone, Like so many people before me, this is my first Ruby Quiz that I am letting out to the world. The interesting thing about my solution is that I managed to come up with most of the previously mentioned optimizations for the BFS type of solution and I came up with one more that has not yet been mentioned. The value I use for deciding when to stop processing nodes because they are too far from the max value is 3*max(start,end). I saw that someone used 2*max+4, so potentially my code could be improved by using that instead. The optimization that I have that I haven't seen anyone else use is always starting with the maximum value and going to the smaller value. This allows a lot of branches to be killed early because the value gets too high. Obviously, switching the start and end value means I need to do num - 2 instead of num + 2 and also reverse the results. Here is the money test on my 1.80 Ghz Pentium M: solve_maze(222, 9999) = [222, 224, 112, 56, 28, 30, 32, 34, 36, 38, 76, 78, 156, 312, 624, 1248, 2496, 2498, 4996, 4998, 9996, 19992, 19994, 9997, 9999] Length: 25 Tree size: 3608 0.120000 0.000000 0.120000 ( 0.120000) Sincerely, Chris Parker --------------------------------------------------------------------------------------------------------------------------------- require 'benchmark' class Integer def odd? return self % 2 == 1 end def even? return !odd? end end #Aliases for more quene like syntax class Array alias deq shift alias enq << end #Node class that knows both its children and parent #Also, takes an intial value and a function to perform on that value #When retrieving value from a node, the action is performed on the value the first time #All subsequent calls to value returns the return value of the first time action was called with value class Node attr_reader :action, :children, :parent def initialize(value, action, parent=nil) @value = value @action = action @children = [] @parent = parent @done_action = false end #find the path to the root node from the current node def get_path_to_root if(parent == nil) return [value] end parent.get_path_to_root<= end_num. This is an optimization because of (3). # It kills many branches early, reducing the number of nodes in the tree. This is done # without breaking anything by making add_two be subtract_two and the results be reversed if start and end are switched. def solve_maze(start_num, end_num) reverse_solution = start_num < end_num if reverse_solution add_two = lambda{ |int| int-2 } start_num,end_num = end_num,start_num else add_two = lambda{ |int| int+2 } end double = lambda{ |int| int*2 } halve = lambda{ |int| int/2 } no_action = lambda{ |int| int } #special case for the start number root = Node.new(start_num, no_action) #keep track of numbers found to prevent repeat work hash = {} #the queue for the BFS q = [root] #start_num is always larger than end_num, numbers larger than this are unlikely to be in #an optimal solution big_val = start_num*3 while q.length != 0 node = q.deq val = node.value if val == end_num solution = node.get_path_to_root solution.reverse! if reverse_solution return [solution, root.tree_size()] end if !hash.has_key?(val) node.children << Node.new(val, add_two, node) if val.abs < big_val node.children << Node.new(val,double,node) if node.action != halve && val.abs < big_val node.children << Node.new(val,halve,node) if val.even? && node.action != double node.children.each{|kid| q.enq(kid)} hash[val] = true end end end if ARGV.length.odd? && !ARGV.length.zero? print "Should be an even number of arguments in the format of start_num end_num [start_num end_num] ...\n" exit end puts Benchmark.measure{ ARGV.each_index do |index| if index.odd? next else start_num = ARGV[index].to_i end_num = ARGV[index + 1].to_i result = solve_maze(start_num, end_num) print "solve_maze(",start_num, ", ",end_num,") = ",result[0].inspect, "\nLength: ",result[0].length, "\nTree size: ",result[1],"\n" end end }