Here's my solution. It's pretty similar in terms of fundamental
algorithm to what's gone before - that is, it simply tries a full
search, then tries again if it hits a dead end, then again. At each
point, it chooses to jump to the square with the fewest remaining open
spots. Nothing that hasn't already been posted.
However, check out this timing for solving a 100x100 search:
real 0m1.640s
user 0m0.121s
sys 0m0.031s
Of course, that's when it solves the problem the first time, which it
does maybe 1/4 of the time. (When it doesn't find a solution the
first time, it seems to take many more re-attempts than one would
expect - I don't have an explanation for that)
My secret is NArray. I was so impressed with its performance on quiz
86 that I decided to use it to tackle this problem too. I'm sure that
there's more I could do to push even more of the algorithm into NArray
internals, but this is pretty fast already.
The best way to explain the code is to look at the different NArray
objects I use:
tracker - this is an n by n array that notes where I've been.
It's 1 for spots I have NOT visited, and 0 for
spots I have.
zoomarray - this is a 13 by 13 array that I use to focus
on the part of tracker that's relevant to
computing where I can go and how many free
spots each of my destinations has. This
simplifies my code, since I don't have to
worry about the current position or whether
I'm close to a wall once I get zoomarray
set up - the current position is always
(6,6) in zoomarray.
jumparray - this is an array with dimensions
(13,13,2,8) - the easiest way to think
about it is as two parallel arrays
that each contain 8 objects the same
shape as zoomarray.
jumparray(true,true,0,n), where n
is from 0 to 7, has a "1" in the
spot where I'd get to if I jumped
in direction number "n".
jumparray(true,true,1,n) has a "1"
in every one of the places I could
jump to if I jumped in direction n.
(See the variable jumppatterns for the meaning of "direction n")
This means that I can do this below in the code:
workarr = zoomarray * jumparray
workarr = workarr.sum(0,1)
with the end result that workarr is a structure like this:
NArray.byte(2,8):
[ [ 0, 0 ],
[ 0, 0 ],
[ 0, 2 ],
[ 1, 3 ],
[ 1, 5 ],
[ 1, 3 ],
[ 0, 2 ],
[ 0, 0 ] ]
The first column tells me if I can jump in that direction at all, and
the second column tells me how many free spots my destination has.
The example is what I get on a 6x6 board when starting in the corner
at (0,0).
To the number of free spots, I add a random number between 0 and 1 so
that I randomize the choice of where to jump in the case of a tie.
Note that I don't use NArray to track my history; among other things,
for speed I wanted to use byte-typed NArrays and that won't work once
we get a board larger than 16 by 16. Instead, I keep my history of
where I've been in one long list and then convert that into a ruby
Array-of-Arrays immediately prior to printing out the solution.
------------penpaper.rb--------------
require 'narray'
n = (ARGV.first||'6').to_i
tracker = NArray.byte(n,n)
jumparray = NArray.byte(13,13,2,8)
jumppatterns = [[-2,-2],[-3,0],[-2,2],
[0,3],[2,2],[3,0],[2,-2],[0,-3]]
jumppatterns.each_with_index{|(x,y),z|
basex,basey = 6+x,6+y
jumparray[basex,basey,0,z]=1
jumppatterns.each{|x2,y2|
jumparray[basex+x2,basey+y2,1,z]=1
}
}
zoomarray = NArray.byte(13,13)
randarray = NArray.float(8)
loopctr = 1
foundit=false
pospath=[]
while !foundit
tracker[] = 1
x=rand((n/2.0).ceil)
y=0
pospath=[[x,y]]
while pospath.size < n*n
tracker[x,y] = 0
zoomarray[] = 0
left,right,top,bottom=x-6,x+6,y-6,y+6
left=0 if left<0
top=0 if top<0
right=n-1 if right>=n
bottom=n-1 if bottom>=n
zoomarray[left-(x-6),top-(y-6)] =
tracker[left..right,top..bottom]
workarr = zoomarray * jumparray
workarr = workarr.sum(0,1)
randarray.random!
randarray.add!(workarr[1,true])
j = randarray.eq(randarray[workarr[0,true]].min).where[0]
if (randarray[j] < 1 and pospath.size < n*n-1)
# drat. Try again
break
end
x += jumppatterns[j][0]
y += jumppatterns[j][1]
pospath << [x,y]
end
if pospath.size == n*n
foundit = true
puts "Found solution on trial #{loopctr}:"
a = Array.new(n){Array.new(n){nil}}
pospath.each_with_index{|(x,y),z| a[x][y]=z+1}
len = 1 + (n*n).to_s.length
puts('-' * (len*n + 2))
a.each{|m|
print '|'
m.each{|s| print("%#{len}d" % s)}
puts '|'
}
puts('-' * (len*n + 2))
else
loopctr += 1
end
end