Here’s my solution that does not roll over the edges. I’ll post that
one in a bit as this one ended up with a lot more modification that
I’d like to throw in that one too.
This follows the Warnsdorff algorithm. This one started out as a
decently clean program although not super-idiomatic Ruby or
particularly OO. In trying to get it to go faster, I have added some
pretty ugly versions of some of the methods. The profiler was telling
me that I spent most of my time in array accessing, since I was
building up a collection of next moves, then filtering them. I
decided to make more of the checks inline instead of building arrays
first. The methods prefixed with fast_ represent the denormalized
version.
Getting rid of those extra array operations took the 500x500 grid
from 47s of user time to 29s of user time!
Bob
RESULTS
[segovia:~/Documents/projects/rubyplay] bobevans% /usr/bin/time -p
ruby quiz90c.rb 10
starting at (7, 7)
Completed!
10 81 32 9 82 48 22 62 49 23
76 27 12 75 26 13 51 25 14 52
33 8 95 86 31 69 85 47 21 61
11 80 77 28 83 78 29 63 50 24
98 91 34 74 94 87 54 70 15 53
38 7 96 79 30 68 84 46 20 60
35 41 99 90 65 71 18 64 57 3
97 92 37 73 93 88 55 1 16 45
39 6 66 40 5 67 58 4 19 59
36 42 100 89 43 72 17 44 56 2
real 0.01
user 0.01
sys 0.00
[segovia:~/Documents/projects/rubyplay] bobevans% /usr/bin/time -p
ruby quiz90c.rb 50
starting at (31, 33)
Completed!
real 0.30
user 0.29
sys 0.00
[segovia:~/Documents/projects/rubyplay] bobevans% /usr/bin/time -p
ruby quiz90c.rb 100
starting at (98, 12)
Completed!
real 1.14
user 1.13
sys 0.00
That one took a few runs to pass. Given the high rate of false starts
on larger grids, it makes sense to implement a different strategy,
perhaps the Conrad-like strategy that Elliot used.
class Grid
SM = 3 # square move length
DM = 2 # diagonal move length
def initialize(size)
@matrix = Array.new(size).map{Array.new(size)}
@counter = 0
@max = size * size
@size = size
end
def solve
no_moves_left = false
pick_start_place
while !no_moves_left && @counter < @max
next_move = pick_move
if next_move.nil?
no_moves_left = true
else
move(next_move[0], next_move[1])
end
end
puts no_moves_left ? "Out of moves :-(" : "Completed!"
print_matrix unless @size > 30
end
def pick_start_place
x = rand(@size)
y = rand(@size)
puts “starting at (#{x}, #{y})”
move(x, y)
end
def pick_move
moves = fast_available_moves
if !moves.empty?
moves.sort[0][1]
else
nil
end
end
def move(x,y)
@x = x
@y = y
# puts “moving to #{x},#{y}”
@matrix[x][y] = @counter += 1
# print_matrix
end
def available_moves
[n(@x,@y), ne(@x,@y), e(@x,@y),
se(@x,@y), s(@x,@y), sw(@x,@y),
w(@x,@y), nw(@x,@y)].reject{ |ma| move_illegal?(ma) || !clear?
(ma[0], ma[1]) }.map { |m| [count_moves(m), m] }
end
def fast_available_moves
moves = []
moves << [fast_count_moves([@x-SM, @y]), [@x-SM, @y]] unless
coord_illegal?(@x-SM) || !clear?(@x-SM, @y)
moves << [fast_count_moves([@x-DM, @y+DM]), [@x-DM, @y+DM]]
unless (coord_illegal?(@x-DM) || coord_illegal?(@y+DM)) || !clear?(@x-
DM, @y+DM)
moves << [fast_count_moves([@x, @y+SM]), [@x, @y+SM]] unless
coord_illegal?(@y+SM) || !clear?(@x, @y+SM)
moves << [fast_count_moves([@x+DM, @y+DM]), [@x+DM, @y+DM]]
unless (coord_illegal?(@x+DM) || coord_illegal?(@y+DM)) || !clear?(@x
+DM, @y+DM)
moves << [fast_count_moves([@x+SM, @y]), [@x+SM, @y]] unless
coord_illegal?(@x+SM) || !clear?(@x+SM, @y)
moves << [fast_count_moves([@x+DM, @y-DM]), [@x+DM, @y-DM]]
unless (coord_illegal?(@x+DM) || coord_illegal?(@y-DM)) || !clear?(@x
+DM, @y-DM)
moves << [fast_count_moves([@x, @y-SM]), [@x, @y-SM]] unless
coord_illegal?(@y-SM) || !clear?(@x, @y-SM)
moves << [fast_count_moves([@x-DM, @y-DM]), [@x-DM, @y-DM]]
unless (coord_illegal?(@x-DM) || coord_illegal?(@y-DM)) || !clear?(@x-
DM, @y-DM)
moves
end
def n(x, y); [x - SM, y]; end
def ne(x, y); [x - DM, y + DM]; end
def e(x, y); [x, y + SM]; end
def se(x, y); [x + DM, y + DM] ; end
def s(x, y); [x + SM, y]; end
def sw(x, y); [x + DM, y - DM] ; end
def w(x, y); [x, y - SM]; end
def nw(x, y); [x - DM, y - DM]; end
def count_moves(m)
x = m[0]
y = m[1]
[n(x, y), ne(x, y), e(x, y),
se(x, y), s(x, y), sw(x, y),
w(x, y), nw(x, y)].reject{ |ma| move_illegal?(ma) || !clear?(ma
[0], ma[1])}.length
end
def fast_count_moves(m)
x = m[0]
y = m[1]
count = 0
count += 1 unless coord_illegal?(x-SM) || !clear?(x-SM, y)
count += 1 unless (coord_illegal?(x-DM) || coord_illegal?(y+DM))
|| !clear?(x-DM, y+DM)
count += 1 unless coord_illegal?(y+SM) || !clear?(x, y+SM)
count += 1 unless (coord_illegal?(x+DM) || coord_illegal?(y+DM))
|| !clear?(x+DM, y+DM)
count += 1 unless coord_illegal?(x+SM) || !clear?(x+SM, y)
count += 1 unless (coord_illegal?(x+DM) || coord_illegal?(y-DM))
|| !clear?(x+DM, y-DM)
count += 1 unless coord_illegal?(y-SM) || !clear?(x, y-SM)
count += 1 unless (coord_illegal?(x-DM) || coord_illegal?(y-DM))
|| !clear?(x-DM, y-DM)
count
end
def clear?(x,y)
@matrix[x][y].nil?
end
def move_illegal?(m)
x = m[0]
y = m[1]
x >= @size || x < 0 || y >= @size || y < 0
end
def coord_illegal?(n)
n >= @size || n < 0
end
def print_matrix
@matrix.each { |r| r.each { |c| printf("%2d ", c)}; puts “\n”}
end
end
def run(size=6)
g = Grid.new(size)
g.solve
nil
end
if $0 == FILE
run(ARGV[0].to_i)
end