Here's my (first) solution (ever) for quiz #60.
I also choose a breadth-first search algorithm because I'm probably
going to need a decent searching algorithm in the near future. To
maximize reusability, I implemented 6 methods in the Integer class
that abstract the problem-specific logic out of the searching
algorithm. When I use a different 'node' class, I will also need to
include the Comparable mix-in and define the <=> method for the
Array.max method on line #40.
The running time for the algorithm is O(N), where N is the number of
integers the algorithm 'visits'. To limit the number of visited
integers, I added a few of the clever optimizations already
mentioned: removing consecutive double/halves and removing adjacent
values greater than a maximum.
$ time ./quiz.rb 22222 99999
[22222, 22224, 11112, 5556, 2778, 2780, 1390, 1392, 696, 348, 174,
87, 89, 91, 93, 95, 97, 194, 388, 390, 780, 1560, 1562, 3124, 6248,
12496, 12498, 24996, 24998, 49996, 49998, 99996, 199992, 199994,
99997, 99999]
real 0m2.098s
user 0m1.868s
sys 0m0.091s
Not faster than Dominik's, but still pretty fast. Perhaps my 1.33
Ghz G4 and 768 MB of RAM is holding my back? :)
Although this is one of many BFS algorithms posted, I'd really
appreciate some feedback on my implementation. Especially in regards
to potential speed ups, missed opportunities for ruby idioms, and
additional optimizations to the adjacency_list method.
~ ryan ~
#!/usr/bin/env ruby
class Integer
attr_reader :distance, :discovered
def odd?
self % 2 != 0
end
# optimized to remove consecutive double/halves and remove
adjacent values greater than a maximum
def adjacency_list(maximum = Infinity)
list = []
list << self * 2 unless @parent == self * 2 or self * 2 > maximum
list << self / 2 unless self.odd? or @parent == self / 2
list << self + 2 unless self + 2 > maximum
list || []
end
def visit!(parent = nil)
@discovered = true
@parent = parent
@distance = parent ? parent.distance + 1 : 0
end
def path_from(start)
if self == start
[start]
else
if @parent == nil
raise "no path from #{start} to #{self} exists"
else
@parent.path_from(start) << self
end
end
end
end
def solve(start, target)
return [start] if start == target
roof = [start, target].max * 2 + 2
start.visit!
queue = [start]
queue.each do |vertex|
vertex.adjacency_list(roof).each do |child|
unless child.discovered
child.visit!(vertex)
return target.path_from(start) if target == child
queue.push(child)
end
end
end
end
# Run this code only when the file is the main program
if $0 == __FILE__
# Parse arguments (authored by James Edward Gray II)
unless ARGV.size == 2 and ARGV.all? { |n| n =~ /\A[1-9]\d*\Z/ }
puts "Usage: #{File.basename($0)} START_NUMBER FINISH_NUMBER"
puts " Both number arguments must be positive integers."
exit
end
start, finish = ARGV.map { |n| Integer(n) }
p solve(start, finish)
end