2

I must mention that this is my first time posting on this site, forgive me if I don't follow this site's guidelines to the tee.

My problem is probably simple but I can't understand it. My knight's tour algorithm recursively finds a path for a knight. It works on index [0,0], it iterates through the spaces of the array perfectly... however, on anything but index [0,0], the program hangs for what seems like an eternity. Here is my code:

# knightstour.py
#
# created by: M. Peele
# section: 01
# 
# This program implements a brute-force solution for the Knight's tour problem 
# using a recursive backtracking algorithm. The Knight's tour is a chessboard 
# puzzle in which the objective is to find a sequence of moves by the knight in 
# which it visits every square on the board exactly one. It uses a 6x6 array for 
# the chessboard where each square is identified by a row and column index, the 
# range of which both start at 0. Let the upper-left square of the board be the 
# row 0 and column 0 square.
#
# Imports the necessary modules.
from arrays import *

# Initializes the chessboard as a 6x6 array. 
chessBoard = Array2D(6, 6)

# Gets the input start position for the knight from the user.
row = int(input("Enter the row: "))
col = int(input("Enter the column: "))

# Main driver function which starts the recursion.
def main():
    knightsTour(row, col, 1)

# Recursive function that solves the Knight's Tour problem.    
def knightsTour(row, col, move):
    # Checks if the given index is in range of the array and is legal.
    if _inRange(row, col) and _isLegal(row, col): 
        chessBoard[row, col] = move # Sets a knight-marker at the given index.
        # If the chessBoard is full, returns True and the solved board.
        if _isFull(chessBoard):
            return True, _draw(chessBoard)    

        # Checks to see if the knight can make another move. If so, makes that 
        # move by calling the function again. 
        possibleOffsets = ((-2, -1), (-2, 1), (-1, 2), (1, 2), \
                           (2, 1), (2, -1), (1, -2), (-1, -2))
        for offset in possibleOffsets:
            if knightsTour(row + offset[0], col + offset[1], move + 1):
                return True 
        # If the loop terminates, no possible move can be made. Removes the 
        # knight-marker at the given index. 
        chessBoard[row, col] = None
        return False 
    else:
        return False

# Determines if the given row, col index is a legal move.
def _isLegal(row, col):
    if _inRange(row, col) and chessBoard[row, col] == None:
        return True
    else:
        return False

# Determines if the given row, col index is in range.
def _inRange(row, col):
    try:
        chessBoard[row, col]
        return True
    except AssertionError:
        return False

# A solution was found if the array is full, meaning that every element in the 
# array is filled with a number saying the knight has visited there.
def _isFull(chessBoard):
    for row in range(chessBoard.numRows()):
        for col in range(chessBoard.numCols()):
            if chessBoard[row, col] == None:
                return False
    return True

# Draws a pictoral representation of the array.
def _draw(chessBoard):
    for row in range(chessBoard.numRows()):
        for col in range(chessBoard.numCols()):
            print("%4s" % chessBoard[row, col], end = " ")
        print()

# Calls the main function.
main()
false
  • 10,264
  • 13
  • 101
  • 209
Miles Peele
  • 325
  • 5
  • 15
  • Please give us an [SSCCE](http://sscce.org). We can't run this without your `arrays` module, and there's a whole lot of code that's probably irrelevant to the problem. – abarnert Apr 08 '13 at 22:46
  • Meanwhile, replacing your `Array2D` with just a `list` of `list`s, your code works for me on every input I throw at it, including (0,1), (0,3), (1,3), (2,5), and (5,2). So, it seems like whatever the problem is, it may be somewhere in your `Array2D` implementation, not this code. – abarnert Apr 08 '13 at 22:52
  • My Array2D module is essentially a list of lists as far as my instructor has said, so if the problem lies with that module, am I just stuck? – Miles Peele Apr 09 '13 at 00:27
  • Can you post the Array2D implementation here, or on pastebin or something? If nobody can run your code, nobody can debug it. – abarnert Apr 09 '13 at 00:36
  • I would if I could, the .pyc module that the Array2D implementation is in is a bunch of dark letters. There's no discernible code – Miles Peele Apr 09 '13 at 00:58
  • A .pyc file is compiled bytecode, not source, so it's not readable without a disassembler or decompiler. But you can still upload it somewhere, and it should work for anyone with the same Python implementation and version as you. (By the way, the very fact that your instructor would give you a .pyc without a .py makes me think that either (a) the real assignment is to discover a bug in his library through white-box testing, or (b) he's completely insane and you should find someone else to learn from…) – abarnert Apr 09 '13 at 01:00
  • Allright, I'll see if I can upload the file to pastebin or a similar website. By the way, thank you for the help, I really do appreciate it. I need to finish this project soon as I have a plethora of other work to do this week and you helping me is easing that work-load if ever so slightly – Miles Peele Apr 09 '13 at 01:11

2 Answers2

1

There is nothing obviously wrong with your code. And, in fact, replacing chessBoard with a list of lists and changing the rest of the code appropriately, it works for all legal inputs.

See this pastebin for the adapted code. See this one for a modified version that just loops over all valid inputs. If you run it, it prints out exactly 36 completed boards.

So, if there is a problem either you're not running the same code you posted here, or there's a bug in your Array2D implementation.


There are a few weird things about your code.

First, you almost never want to check == None. If you really need to check whether something is None, use the is operator, not ==. If all of your "real" values are truthy, just use the value itself as a boolean (because None is falsey). See Programming Recommendations in PEP 8 for details.

Next, you have your global setup split between a main function, and global module scope. Usually you want to do it all in the same place.

Finally, having a recursive function that mutates a global variable is an odd thing to do. Not that it doesn't work in your case (but only because you can only ever run one test before exiting), but usually in a recursive function you want to either pass the value down as an "accumulator" argument, or do everything immutably (by passing copies down and back up).

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Thanks for the response. You say that there's nothing wrong with my code, but forgive me if I can't see that, because not all inputs work. In fact, I put in [3, 3] in for the index of the array about ~1 hour ago and my program is still running. I'm 99.99% positive that the code I posted is the same as the one I have running, and my Array2D implementation was provided by my instructor so I can't modify that. I'll make sure to clean up the weird things about my code, though. – Miles Peele Apr 09 '13 at 00:26
  • @MilesPeele: Have you tested the version(s) I uploaded to pastebin to see what they do for [3, 3]? Also, it's always possible that, in converting your code to use a list of lists explicitly, I accidentally fixed a bug without noticing it. (For example, if `Array2D` is 1-based, or allows you to access `end` instead of just `end-1`, or…?) So, you should look over my changes to make sure they're as trivial as intended. – abarnert Apr 09 '13 at 00:38
  • I actually haven't tested those as I can't access them - chrome says "the webpage is not available" when I try to click your hotlink. And I just tested other random indices, an example being [0,4] and they work, but [3,3] is still failing. I'm beginning to consider that either it's not possible to solve it for the given index or the time it takes to solve it is unreasonable. – Miles Peele Apr 09 '13 at 00:45
  • @MilesPeele: I re-posted the first one as http://pastebin.com/mUkrTTWR to see if you can reach that. If not, maybe your uni/company/ISP/government is blocking pastebin for some reason? Meanwhile, as I said, my second version tests all 36 combinations of [0-5, 0-5], and they all succeed. And I just tested the first version with [3,3] and it succeeds within a fraction of a second. It is quite possible that your algorithm won't terminate for an input that has no solution, but there are no apparently inputs on a 6x6 board that have no solution with your algorithm, so it doesn't matter. – abarnert Apr 09 '13 at 00:57
  • I just tested your pastebin.com code and true to your word, everything works perfectly. There must be a problem with the Array2D implementation then, I must not be using it correctly. Thank you again for all your help, it's greatly appreciated. – Miles Peele Apr 09 '13 at 02:17
0

The reason abarnert's code works is that his function, when rewritten accesses the array using a negative index. so, if you look at the results, they are incorrect (the knight jumps from the top to the bottom of the board).

Bob
  • 1