[QUIZ] Numeric Maze (#60)

P

Phil Tomson

OK, so I didn't have time to code it up and see for sure, what with
some transatlantic travel and New Year's and all that stuff, but the
first thing that struck me when reading the problem was "dynamic
programming" - did anyone take this route and hence give me a bit of
satisfaction that my instincts were right?

I kept thinking that a recursive solution would be the easiest way to go, but
I quickly found that even some of the most trival testcases overwhelmed the
stack :-( (not too surprising, I guess, given the size of the search space).

If I have time to get it done I'll post my non-recursive version (that is
unfortuneately becoming quite bloated, I think).

Phil
 
K

Kero

I get for this for (222, 9999) --
[222, 224, 112, 56, 28, 14, 16, 18, 36, 38, 76, 78, 156, 312, 624,
1248,
2496, 2498, 4996, 4998, 9996, 19992, 19994, 9997, 9999]

My code dies a horrible death on this one. <laughs> I think I get a
right answer, but the wait was embarrassing:

$ time ruby numeric_maze.rb 222 9999
222, 224, 112, 56, 58, 29, 31, 33, 35, 37, 39, 78, 156, 312, 624,
1248, 2496, 2498, 4996, 4998, 9996, 19992, 19994, 9997, 9999

real 96m13.978s
user 49m27.502s
sys 46m22.817s

Guess I need more than my one optimization now. <laughs>

I stopped at 222 -> 4998 which took 43 seconds. Each next step will take
a rough factor 3, so I'll end up near 3 hours. Oops...

First pruning... Slices running time to less than 0.2 seconds *wheee* and to
9999 in slightly more than 0.2 seconds (which proves I got rid of that nasty
exponential thing).
 
S

Steven Aerts

Hi all,

hereby my first solution for the ruby quiz and my first real world ruby
program. :)

So all comments about my ruby-coding-style are very welcome(especially how
you think about using the throw catch statement).

The program itself runs two depth first searches in parallel one from the
start number and one from the end number looking for the middle number in the
end-result.
When found, the two paths to get to this middle number form the end-result.

It is possible to use the same method to calculate both pathways by using
negative numbers for the path from the end-point.
--
Hope you like it,

Steven <[email protected]>

--
class NumericMaze
def solve (from, to)
return [from] if from == to

@done = {from => :from, -to => :to}
@todo = [from, -to]

catch :found do
while true
t = @todo.shift

addEdge(2*t, t)
addEdge(t+2, t) if (t <- 2) || (0 <= t)
addEdge(t/2,t) if t.modulo(2) == 0
end
end
return @result
end

def addEdge(new, from)
return if @done[new] != nil

@done[new] = from

if @done[-new] then #path found
@result = calcPath(new.abs)
throw :found
end

@todo.push new
end

def calcPath(middle)
pathway = [middle]

t = middle
pathway.unshift(t) until (t = @done[t]) == :from

t = -middle
pathway.push(-t) until (t = @done[t]) == :to

return pathway
end
end
 
C

Christer Nilsson

Ilmari said:
#
# Problems appear when trying to make 255 into 257:
# 11111111 -> 100000001
#
# The shortest way is by adding 2.
# But the algorithm below fails at that and goes the long way:
# 11111111 << 1
# 111111110 + 2
# 1000000000 + 2
# 1000000010 >> 1
# 100000001
#

Ilmari, I tried to replicate your solution, before seeing it, and
strangely run into exactly the same behaviour. The code is not complete,
but the idea is to go downwards to 1 with both numbers. I'm using the
complement of +2, -2 for the target number.

t up(255), [255, 510, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]
--> --> --v
<-- <-- <--
t down(257), [257, 514, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]

Having these two sequences, I find the largest common number, in this
case 512. This gives me the non optimal solution [255,510,512,514,257],
which is exactly the same as yours. I'm not sure if identifying
shortcuts afterwards, will find the shortest path, though, in all cases.
(Finding 255 + 2 = 257, is easy, but what about finding 255 -> 259 if
257 is missing.)

I tried the six digit subproblem of your big problem, and it worked out
perfectly optimal. So I was surprised 255 -> 257 behaved so bad.

Christer


def down(x)
return [] if x==0
return [1] if x==1
return [2,1] if x==2
return [x] + down(x*2) if x.modulo(2)==1
return [x] + down(x/2) if x.modulo(4)==0
[x] + down(x-2)
end

def up(x)
return [] if x==0
return [1] if x==1
return [2,1] if x==2
return [x] + up(x*2) if x.modulo(2)==1
return [x] + up(x/2) if x.modulo(4)==0
[x] + up(x+2)
end

require 'test/unit'
class TestIlmari < Test::Unit::TestCase
def t (actual, expect)
assert_equal expect, actual
end
def test_all
t up(255), [255, 510, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]
t down(257), [257, 514, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1]
#t up(72), [72,36,18,20,10,12,6,8,4,2,1]
#t down(28), [28,14,12,6,4,2,1]
#t up(150128850109293).size,82
#t down(8591982807778218492).size, 100
end
end
 
J

James Edward Gray II

I kept thinking that a recursive solution would be the easiest way
to go, but
I quickly found that even some of the most trival testcases
overwhelmed the
stack :-( (not too surprising, I guess, given the size of the
search space).

This seems odd to me. Wouldn't a recursive breadth-first (or even
iterative depth-first) only ever recurse as deep as there are numbers
in the solution? Everything we've seen so far should be well within
solvable that way, unless I'm missing something obvious here...

James Edward Gray II
 
J

James Edward Gray II

So all comments about my ruby-coding-style are very welcome
(especially how
you think about using the throw catch statement).

Looking good so far. Here's some general comments:

1. We usually do variable and method names in "snake_case" style and
class names in "CamelCase" (as you had them).
2. Most of us write infinite loops in Ruby as loop do ... end.

These are just style nitpicks of course though, so don't take them
too seriously. ;)

James Edward Gray II
 
R

Reinder Verlinde

Justin Bishop said:
i had toyed around with other optimizations but none seemed elegant
and time-saving enough to be worth it. what i was hoping for was a
nice way to identify when certain paths are careening insanely
off-path and clearly won't be the correct answer...i have yet to see
the solution that can do things like solve(222,9999) in
milliseconds...i am anxiously awaiting one...or did i miss it?

I think one should look at the mathematical side of the problem first.
Here are some starting ideas:

- write both the starting and the target number in binary
- notice that the three operators do the following:
- add a zero to the right = left shift the number one position
- remove a zero from the right = right shift the number one position
- flip a bit in the one-but-rightmost position, possibly changing
consecutive ones to zeroes to the left of this bit

Only the first operation can possibly increase the number of zeroes in
the number (always by one).

Only the second one can change the parity of the number.

Only the last one can possibly change the number of ones in the number
(either increasing it by one, or decreasing it by possibly arbitrary
amounts)

Looking at the example (222,9999), I see the following:
222 = 0x00DE = 1101 1110 (2 zeroes, 6 ones)
9999 = 0x270F = 10 0111 0000 1111 (5 zeroes, 8 ones)

From this, we can see that we must do at least 3 left shifts (to obtain
the five zeroes), at least one right shift (to change parity), and at
least one '+2' (to change the number of ones)

Looking at it in a different way: we must get a '1' in position 14 from
the right. That '1' can only come from position 13, using either the
'*2' or the '+2' (with sufficient overflows) operations.

There is no '1' in position 13. To get one there, we need one at
position 12, etc.

In the end to get a '1' in position 14 is by moving the one in position
8 there, using a combination of at least six '*2' or '+2' operations.
If we do that using '*2' only, the '1' in position 7 would move to
position 13. We do not want a '1' there, so we should use '+2' at least
once.

I think logic like this will lead to a better understanding of the
problem, and possibly to more efficient solutions.

For instance: I would guess that many of the optimal solutions are
rather dull, ending in a series of ('*2') or ('+2', '*2') operations to
shift in zero and one bits, followed by a single '/2' if the target
value is odd (WARNING: this is just a hunch; I may be completely wrong
here)

Reinder
 
K

Kenneth Collins

Gregory said:
This is my first Ruby quiz. I was hoping to have the whole thing done
by the end of the first 48 hours, but I kept getting hung up on edge
cases.
I did figure out the binary approach on my own, though.

Gregory, this is an amazing solution! However I got a failure trying
some arbitrarily selected src and dst values:

$ ruby test60.rb 4934939 39329291
/60.rb:47:in `halve': Trying to halve an odd number: 49 (RuntimeError)
from ./60.rb:127:in `solve'
from test60.rb:7
 
K

Kenneth Collins

Kenneth said:
Gregory, this is an amazing solution! However I got a failure trying
some arbitrarily selected src and dst values:

$ ruby test60.rb 4934939 39329291
./60.rb:47:in `halve': Trying to halve an odd number: 49 (RuntimeError)
from ./60.rb:127:in `solve'
from test60.rb:7

Another case with smaller numbers:

$ ruby test60.rb 49 64
/60.rb:47:in `halve': Trying to halve an odd number: 49 (RuntimeError)
from ./60.rb:127:in `solve'
from test60.rb:7
 
K

Kero

My solution's similar in style [breadth first, don't duplicate search],
though I've also roof-limited it so it can't explore the search space
above 2*[max(target,start)] + 4 which provides a fair speed up for e.g.
222, 9999. I'm not sure if that loses the optimality property though.

I use
@@max = [(source + 2) * 2, target * 2].max
which matches your roof almost exactly (The small assymetry is due to the
missing sub2 operator, naturally.). The speedup is huge, as you can see
in some other posting of mine. I'm not entirely certain about the optimality
property, but look at the following example:

13, 15, 30, 32, 16, 8 # what I thought of myself, pushing through the roof
13, 26, 28, 14, 16, 8 # what the program found, not going through the roof

Where the point is that an odd number either +1 or +3 can always be halved
twice, and there is no point in adding two first (though maybe in between).

I'm even thinking
[source*2+2, target*2].max
could be the roof, but hey, what's 2 more or less.

Ah well, this is not my field of expertise. Somebody can shed more light on
this?

Bye,
Kero.
 
C

Chris Parker

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<<value
end

def tree_size
#remember that if there are no children, aka this is a leaf node,
this returns 1, the initial value of result
return children.inject(1){|result,child| result + child.tree_size }
end

def value
if(!@done_action)
@done_action = true
return @value = @action.call(@value)
end
return @value
end

#print tree in a stringified array format
def tree_as_array
print "%d" % value
print "[" if children.length != 0
children.each_with_index{|child, index| child.tree_as_array; print
", " if index != children.length - 1}
print "]" if children.length != 0
end

end

#Solves the numeric maze with a bunch of optimizations
#Optimizations:
#(1) if parent action was halve, no child should be double
#(2) if parent action was double, no child should halve
#(3) if value of current node is greater than 3 times the
max(start_num, end_num), don't double or add 2
#(4) if value of current node has already been found, stop processing
this node
#(5) start_num should always be >= 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
}
 
G

Gregory Seidman

} > Gregory Seidman wrote:
} >> This is my first Ruby quiz. I was hoping to have the whole thing done
} >> by the end of the first 48 hours, but I kept getting hung up on edge
} >> cases.
} >> I did figure out the binary approach on my own, though.
} >>
} >
} > Gregory, this is an amazing solution! However I got a failure trying
} > some arbitrarily selected src and dst values:
} >
} > $ ruby test60.rb 4934939 39329291
} > ./60.rb:47:in `halve': Trying to halve an odd number: 49 (RuntimeError)
} > from ./60.rb:127:in `solve'
} > from test60.rb:7
}
} Another case with smaller numbers:
}
} $ ruby test60.rb 49 64
} ./60.rb:47:in `halve': Trying to halve an odd number: 49 (RuntimeError)
} from ./60.rb:127:in `solve'
} from test60.rb:7

Ratzafratz. I'll look into it. Did I mention running into problems with
edge cases? Oy. Thanks for the test cases.

--Greg
 
I

Ilmari Heikkinen

T24gMS8yLzA2LCBHcmVnb3J5IFNlaWRtYW4gPGdzc2xpc3QrcnVieUBhbnRocm9wb2hlZHJvbi5u
ZXQ+IHdyb3RlOgo+IEknbSBwcmV0dHkgc3VyZSB0aGUgcmVzdWx0cyBhcmUgZ3VhcmFudGVlZCB0
bwo+IGJlIG9wdGltYWwsIHRvby4KPgoKJCBydWJ5IHRlc3Q2MC5yYiAyNTUgMjU3ClsyNTUsIDUx
MCwgNTEyLCA1MTQsIDI1N10KCk9wcyA9IDQ6IGRhYWgKClNhbWUgcHJvYmxlbSBJIHJhbiBpbnRv
IDopCg==
 
J

J. Ryan Sobol

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
 
D

Dominik Bathon

For this quiz I took what I made for the word chain quiz and modified
it a bit.

I did that, too ;-)

My solution uses unidirectional BFS (like many others). The NumericMaze =20
class is generic, meaning that it can also work with other ops, so =20
naturally it doesn't have any optimizations for this special problem. I =20
only limit the search space in the "if $0 =3D=3D __FILE__" part.

Dominik


class NumericMaze

OP_DOUBLE =3D lambda { |x| x * 2 }
OP_HALVE =3D lambda { |x| x % 2 =3D=3D 0 ? x / 2 : nil }
OP_ADD_TWO =3D lambda { |x| x + 2 }

# ops is an array of lambdas, each lambda returns a next step for a =
=20
given
# number, or nil if no next step is possible for the given number
def initialize(ops =3D [OP_DOUBLE, OP_HALVE, OP_ADD_TWO])
@ops =3D ops
end

def solve(start, target, max_num =3D nil)
# build chain with simple breadth first search
current =3D [start]
return current if start =3D=3D target
pre =3D { start =3D> nil } # will contain the predecessors
catch:)done) do
until current.empty?
next_step =3D []
current.each do |num|
@ops.each do |op|
unless (step_num =3D op[num]).nil?
# have we seen this number before?
unless pre.has_key?(step_num) ||
(max_num && step_num > max_num)
pre[step_num] =3D num
throw:)done) if step_num =3D=3D target
next_step << step_num
end
end
end
end
current =3D next_step
end
return nil # no chain found
end
# build the chain (in reverse order)
chain =3D [target]
chain << target while target =3D pre[target]
chain.reverse
end

end


if $0 =3D=3D __FILE__
a, b, =3D *ARGV.map { |str| Integer(str) }
p NumericMaze.new.solve(a, b, [a, b].max.abs * 3)
end
 
D

Dominik Bathon

$ time ./quiz.rb 22222 99999
[22222, 22224, 11112, 5556, 2778, 2780, 1390, 1392, 696, 348, 174, 87, = =20
89, 91, 93, 95, 97, 194, 388, 390, 780, 1560, 1562, 3124, 6248, 12496, = =20
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 = =20
G4 and 768 MB of RAM is holding my back? :)

It actually is faster than my version:

$ time ruby ryan_org.rb 22222 99999
[22222, 22224, 11112, 5556, 2778, 2780, 1390, 1392, 696, 348, 174, 87, 89=
, =20
91, 93, 95, 97, 194, 388, 390, 780, 1560, 1562, 3124, 6248, 12496, 12498,=
=20
24996, 24998, 49996, 49998, 99996, 199992, 199994, 99997, 99999]

real 0m1.197s
user 0m1.138s
sys 0m0.026s

(Pentium M 1.5)
Although this is one of many BFS algorithms posted, I'd really =20
appreciate some feedback on my implementation. Especially in regards t= o =20
potential speed ups, missed opportunities for ruby idioms, and =20
additional optimizations to the adjacency_list method.

Here is a patch that speeds it up a bit:

--- ryan_org.rb 2006-01-02 16:47:20.000000000 +0100
+++ ryan.rb 2006-01-02 16:47:16.000000000 +0100
@@ -1,5 +1,5 @@
class Integer
- attr_reader :distance, :discovered
+ attr_reader :parent

def odd?
self % 2 !=3D 0
@@ -15,9 +15,7 @@
end

def visit!(parent =3D nil)
- @discovered =3D true
@parent =3D parent
- @distance =3D parent ? parent.distance + 1 : 0
end

def path_from(start)
@@ -40,7 +38,7 @@
queue =3D [start]
queue.each do |vertex|
vertex.adjacency_list(roof).each do |child|
- unless child.discovered
+ unless child.parent
child.visit!(vertex)
return target.path_from(start) if target =3D=3D child
queue.push(child)

It basically avoids some instance variable sets and removes distance, =20
which is unused.

$ time ruby ryan.rb 22222 99999
[22222, 22224, 11112, 5556, 2778, 2780, 1390, 1392, 696, 348, 174, 87, 89=
, =20
91, 93, 95, 97, 194, 388, 390, 780, 1560, 1562, 3124, 6248, 12496, 12498,=
=20
24996, 24998, 49996, 49998, 99996, 199992, 199994, 99997, 99999]

real 0m1.031s
user 0m0.963s
sys 0m0.020s

I think it is an interesting idea to avoid using a hash, by storing the =20
parent info in an instance variable of the Fixnums and it seems to be =20
quite fast. But it has one big downside: solve only works once, because =20
Fixnums are immediate values:

$ irb -r ryan
irb(main):001:0> solve 22222, 99999
=3D> [22222, 22224, 11112, 5556, 2778, 2780, 1390, 1392, 696, 348, 174, 8=
7, =20
89, 91, 93, 95, 97, 194, 388, 390, 780, 1560, 1562, 3124, 6248, 12496, =20
12498, 24996, 24998, 49996, 49998, 99996, 199992, 199994, 99997, 99999]
irb(main):002:0> solve 22222, 99999
=3D> [22222]

Dominik
 
0

0x002A

okay here's my shot at the quiz, it's quite large for such a simple
problem, but i couldn't resist to use
:* in my code ;-)

class Integer
def even?
self % 2 == 0
end

def odd?
not even?
end
end

# solves rubyquiz #60
class Solver
def initialize(start, goal)
@start, @goal = start, goal
@visited = {}
@queue = [[@goal, []]]
@ops = []
[AddTwo, Double, Halve].each {|op| @ops <<
op.new(@start, @goal) }
end

# are we there yet?
def done_with?(temp_goal)
@start == temp_goal
end

# transforms the carried steps into a valid solution
def solution(steps)
steps.reverse.unshift @start
end

# does all the work
def solve
# there should be a better way to recover the steps
than carrying them
# around in the search tree (depth first search for
example?)
while current = @queue.shift
temp_goal, steps = *current # parallel
assignment in conditionals won't work

return solution(steps) if done_with? temp_goal
next if @visited[temp_goal] # been there, done
that

#puts "#{@start} -> #{@goal}: testing
#{temp_goal}, steps so far: #{steps.join(" ")}"
#gets

@visited[temp_goal] = true
new_steps = steps + [temp_goal]

@ops.each do |op|
@queue << [op.apply(temp_goal),
new_steps] if op.makes_sense? temp_goal
end
end
# my guess is, that there's always a solution. any
proof?
raise "no solution found"
end

# creates a new solver and attempts to solve(a,b)
def self.solve(a,b)
new(a,b).solve
end
end

# Applies a method with the argument 2
# maybe too much OO? :)
class TwoOperation
def initialize(start, goal)
@start, @goal = start, goal
end

def apply(temp_goal)
temp_goal.send(@meth, 2)
end

def makes_sense?
false
end
end

# inverse of double
class Double < TwoOperation
def initialize(start, goal)
super
@meth = :/
end

def makes_sense?(temp_goal)
temp_goal.even?
end
end

# inverse of halve
class Halve < TwoOperation
def initialize(start, goal)
super
@meth = :* # heh a kissing smiley, ruby is soo cute
end

def makes_sense?(temp_goal)
# was (@goal < @start and temp_goal.even?) or (not
temp_goal.even?)
temp_goal.odd? or @goal < @start
end
end

# inverse of add_two
class AddTwo < TwoOperation
def initialize(start, goal)
super
@meth = :-
end

def makes_sense?(temp_goal)
temp_goal > 1
end
end

# for the testcases
def solve(a, b)
Solver.solve(a,b)
end
 
B

Brian Takita

------=_Part_14920_28346379.1136235424730
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Actually this seems to be a better view of the pattern.
Notice that the leftmost bits are coaxed into matching the leftmost bits of
the solution first, and then work to the right.

11011110 222
11100000 224
1110000 112
111000 56
11100 28
1110 14
10000 16
10010 18
100100 36
100110 38
1001100 76
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
10011100001110 9998
100111000011100 19996
100111000011110 19998
10011100001111 9999

11011110 222
11100000 224
1110000 112
111000 56
11100 28
1110 14
10000 16
10010 18
100100 36
100110 38
1001100 76
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
100111000011000 19992
100111000011010 19994
10011100001101 9997
10011100001111 9999

11011110 222
11100000 224
1110000 112
111000 56
11100 28
11110 30
1111 15
10001 17
10011 19
100110 38
1001100 76
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
10011100001110 9998
100111000011100 19996
100111000011110 19998
10011100001111 9999

11011110 222
11100000 224
1110000 112
111000 56
11100 28
11110 30
1111 15
10001 17
10011 19
100110 38
1001100 76
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
100111000011000 19992
100111000011010 19994
10011100001101 9997
10011100001111 9999

11011110 222
11100000 224
1110000 112
111000 56
11100 28
11110 30
100000 32
100010 34
100100 36
100110 38
1001100 76
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
10011100001110 9998
100111000011100 19996
100111000011110 19998
10011100001111 9999

11011110 222
11100000 224
1110000 112
111000 56
11100 28
11110 30
100000 32
100010 34
100100 36
100110 38
1001100 76
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
100111000011000 19992
100111000011010 19994
10011100001101 9997
10011100001111 9999

11011110 222
11100000 224
1110000 112
111000 56
111010 58
11101 29
11111 31
100001 33
100011 35
100101 37
100111 39
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
10011100001110 9998
100111000011100 19996
100111000011110 19998
10011100001111 9999

11011110 222
11100000 224
1110000 112
111000 56
111010 58
11101 29
11111 31
100001 33
100011 35
100101 37
100111 39
1001110 78
10011100 156
100111000 312
1001110000 624
10011100000 1248
100111000000 2496
100111000010 2498
1001110000100 4996
1001110000110 4998
10011100001100 9996
100111000011000 19992
100111000011010 19994
10011100001101 9997
10011100001111 9999

------=_Part_14920_28346379.1136235424730--
 

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

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top