When I was a kid, I used to play chess with my older brothers. Since they were
much older than me, I was more or less a "punch bag", and was losing the matches
in (usually) a matter of minutes. I was persistent though... and saddened by defeat
after defeat, I occasionally "escaped" into the solitary game of the Knight's
Tour. From my local copy of Wikipedia:

The Knight's Tour is a mathematical problem involving a knight on a chessboard.
The knight is placed on the empty board and, moving according to the rules of chess,
must visit each square exactly once.

It was easy to understand, easy to try, but not so easy to accomplish.
And it only required a pen and a piece of paper. Whenever I managed to
complete all 64 squares (by shear luck), I always felt a surge of exhilaration.

Nowadays, I've more or less abandoned chess... But strangely enough,
I remembered my childhood refuge once again today, just by seeing some
Sudoku puzzle in a magazine... and decided to take another swing at it.
With much better tools than pen and paper :‑)

Python attacks Knight

#!/usr/bin/env pythonimport sysg_sqSize =-1# the board size, passed at runtimeg_board =[]# the board will be constructed as a list of listsdefmain():global g_sqSizeiflen(sys.argv)!=2: g_sqSize =8# Default: Fill the normal 8x8 chess boardelse:try: g_sqSize =int(sys.argv[1])# or, the NxN the user wantsexcept:print("Usage: "+ sys.argv[0]+" <squareSize>") sys.exit(1)for i inrange(0, g_sqSize): g_board.append(g_sqSize*[0])# Fill the board with zeroesFill(0,0,1)# Start the recursion with a 1 in the upper leftprint("No solution found")# if the recursion returns, it faileddefInRangeAndEmpty(ty,tx):# check if coordinates are within the boardreturn ty>=0and tx>=0and ty<g_sqSize and tx<g_sqSize \and g_board[ty][tx]==0# and the square is emptydefFill(y,x,counter):# The recursive function that fills the boardassert g_board[y][x]==0 g_board[y][x]= counter # Fill the squareif counter == g_sqSize*g_sqSize:# Was this the last empty square?PrintBoard()# Yes, print the board... sys.exit(1)# ...and exit jumps =((-2,1),(-1,2),(1,2),(2,1),(2,-1),(1,-2),(-1,-2),(-2,-1))for jump in jumps:# otherwise, try all the empty neighbours in turn ty,tx = y+jump[0], x+jump[1]ifInRangeAndEmpty(ty,tx):Fill(ty,tx,counter+1)# *** RECURSION! *** g_board[y][x]=0# if we get here, all the neighbours failed,# so reset the square and returndefPrintBoard():# print the board using nice ASCII art ('+' and '-') scale =len(str(g_sqSize*g_sqSize))print(g_sqSize*("+"+ scale*"-")+"+")for line in g_board:for elem in line: sys.stdout.write("|%*d"%(scale,elem))print("|\n"+g_sqSize*("+"+ scale*"-")+"+")if __name__ =="__main__":main()

The code performs a recursive attempt to Fill the board.
Starting at the top left corner, it proceeds to identify the empty neighbours,
trying each one of them in turn.

...only for small boards. It solves the 5x5 and 6x6 boards in less than
a second, but it takes 96 seconds to solve the 7x7 and 28 seconds to solve
the 8x8... I didn't even try with larger board sizes, since it was
clear that naive recursion is too slow.

I know, this code has 3 lambdas in 3 successive lines, I've
gone a little too far :‑) Well, bear with me, what it does is in fact very simple.
It sorts the emptyNeighbours list according to how "lonely" this neighbour
is, that is, according to how many empty neighbours he has. To do that,
it uses map to apply a lambda to all the elements of jumps:
the innermost lambda checks in turn all neighbours to see if (a) they are within the
board and (b) they are empty. If a neighbour fulfils both, the lambda returns
1, otherwise it returns 0. reduce is then used to accumulate
all 1s (up to a maximum of 8) and return their sum (via the lambda x,y: return x+y).
Finally, we use this calculation process as the sort key for the call to
sorted(emptyNeighbours). In that way, the neighbouring empty squares
that have the least "free space" around them, are used first during the
recursion.

Python also offers the sum function to implement the accumulation
process, and it also offers list comprehensions to ease up the syntax further:

By using this simple policy (which I clearly remember I was also following
when I was a kid!), the solving time became less than 100 milliseconds,
for all square sizes up to 31x31! Beyond 31x31, at least on my
manually-compiled Python interpreter, the program run out of stack space and
required an increase in Python's recursion limit (via sys.setrecursionlimit).
After that little tweak, the 100x100 problem was solved just as quick :‑)

P.S. Not to be paternalistic, but allow me to suggest writing this sorting
code in C (using qsort?), or in C++ using STL's sort algorithm. When you finish,
look at the code you wrote, and then look back at the Python code in its final
form: 2 lines to implement the sorting! You'll have to agree that writing functions
just to pass to qsort (or functors in order to pass to std::sort) is... well,
far behind. Python syntax has tremendous advantages...
Advantages that have nothing to do with libraries, and can be traced back to the
combination of (a) functional programming concepts and (b) the perfect syntax that
Python offers. I would truly be amazed to see anyone writing the same sorting logic
in C++ in anything less than 3 times the lines of code I wrote for it in Python.
And even if this is somehow possible (using external libraries like BOOST, I'd wager),
the sorting code will take longer to write, and it will be far more difficult to
comprehend or refactor...

It took me less than an hour to write this, and I'm a "casual user" of Python,
not a Python expert...

The comments on this website require the use of JavaScript. Perhaps your browser isn't
JavaScript capable or the script is not being run for another reason. If you're
interested in reading the comments or leaving a comment behind please try again with a
different browser or from a different connection.