import random
class Solver:
def __init__(self, n: int, possibleMoves: list):
self.__n = n
self.__nSquared = n * n
self.__possibleMoves = possibleMoves
self.__orderTable = None
def Solve(self, startPosition: tuple = (-1, -1), pickMoveCandidatesInRandomOrder: bool = True) -> bool:
self.__orderTable = [[0 for x in range(self.__n)] for y in range(self.__n)]
x, y = startPosition
if x == y == -1:
x, y = random.randrange(self.__n), random.randrange(self.__n)
self.__orderTable[y][x] = 1
for alreadyOrdered in range(1, self.__nSquared):
nx, ny = self.__NextFrom(x, y, pickMoveCandidatesInRandomOrder)
if nx == ny == -1:
return False
self.__orderTable[ny][nx] = alreadyOrdered + 1
x, y = nx, ny
return True
def PrintSolution(self):
maxDigits = len(str(self.__nSquared))
for row in self.__orderTable:
print(' '.join((f'{value:{maxDigits}}' for value in row)))
def __NextFrom(self, x, y, pickMoveCandidatesInRandomOrder: bool):
nx, ny = -1, -1
minDegree = len(self.__possibleMoves) + 1
validDestinations = self.__ValidDestinationsFrom(x, y)
if pickMoveCandidatesInRandomOrder:
validDestinations = self.__ValidDestinationsInRandomOrderFrom(x, y)
for dstX, dstY in validDestinations:
dstDegree = self.__CalculateDegreeOf(dstX, dstY)
if dstDegree < minDegree:
minDegree = dstDegree
nx, ny = dstX, dstY
return nx, ny
def __InBounds(self, x, y):
return 0 <= x < self.__n and 0 <= y < self.__n
def __ValidDestinationsFrom(self, x, y):
for dx, dy in self.__possibleMoves:
nx, ny = x + dx, y + dy
if self.__InBounds(nx, ny) and self.__orderTable[ny][nx] == 0:
yield nx, ny
def __ValidDestinationsInRandomOrderFrom(self, x, y):
validDestinations = list(self.__ValidDestinationsFrom(x, y))
random.shuffle(validDestinations)
return validDestinations
def __CalculateDegreeOf(self, x, y):
return sum(1 for dst in self.__ValidDestinationsFrom(x, y))
if __name__ == '__main__':
n = 10
moves = [(3, 0), (2, 2), (0, 3), (-2, 2), (-3, 0), (-2, -2), (0, -3), (2, -2)]
# moves = [(1, 0), (0, 1), (-1, 0), (0, -1)]
solver = Solver(n, moves)
while True:
if solver.Solve(startPosition=(0, 0), pickMoveCandidatesInRandomOrder=False):
solver.PrintSolution()
break
iterationsCount = 1000
successCount = sum(int(solver.Solve(pickMoveCandidatesInRandomOrder=False)) for i in range(iterationsCount))
print(f'Success in {successCount} of {iterationsCount} iterations without random candidates ordering.')
successCount = sum(int(solver.Solve(pickMoveCandidatesInRandomOrder=True)) for i in range(iterationsCount))
print(f'Success in {successCount} of {iterationsCount} iterations with random candidates ordering.')
aW1wb3J0IHJhbmRvbQoKCmNsYXNzIFNvbHZlcjoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBuOiBpbnQsIHBvc3NpYmxlTW92ZXM6IGxpc3QpOgogICAgICAgIHNlbGYuX19uID0gbgogICAgICAgIHNlbGYuX19uU3F1YXJlZCA9IG4gKiBuCiAgICAgICAgc2VsZi5fX3Bvc3NpYmxlTW92ZXMgPSBwb3NzaWJsZU1vdmVzCiAgICAgICAgc2VsZi5fX29yZGVyVGFibGUgPSBOb25lCgogICAgZGVmIFNvbHZlKHNlbGYsIHN0YXJ0UG9zaXRpb246IHR1cGxlID0gKC0xLCAtMSksIHBpY2tNb3ZlQ2FuZGlkYXRlc0luUmFuZG9tT3JkZXI6IGJvb2wgPSBUcnVlKSAtPiBib29sOgogICAgICAgIHNlbGYuX19vcmRlclRhYmxlID0gW1swIGZvciB4IGluIHJhbmdlKHNlbGYuX19uKV0gZm9yIHkgaW4gcmFuZ2Uoc2VsZi5fX24pXQoKICAgICAgICB4LCB5ID0gc3RhcnRQb3NpdGlvbgogICAgICAgIGlmIHggPT0geSA9PSAtMToKICAgICAgICAgICAgeCwgeSA9IHJhbmRvbS5yYW5kcmFuZ2Uoc2VsZi5fX24pLCByYW5kb20ucmFuZHJhbmdlKHNlbGYuX19uKQogICAgICAgIHNlbGYuX19vcmRlclRhYmxlW3ldW3hdID0gMQoKICAgICAgICBmb3IgYWxyZWFkeU9yZGVyZWQgaW4gcmFuZ2UoMSwgc2VsZi5fX25TcXVhcmVkKToKICAgICAgICAgICAgbngsIG55ID0gc2VsZi5fX05leHRGcm9tKHgsIHksIHBpY2tNb3ZlQ2FuZGlkYXRlc0luUmFuZG9tT3JkZXIpCiAgICAgICAgICAgIGlmIG54ID09IG55ID09IC0xOgogICAgICAgICAgICAgICAgcmV0dXJuIEZhbHNlCiAgICAgICAgICAgIHNlbGYuX19vcmRlclRhYmxlW255XVtueF0gPSBhbHJlYWR5T3JkZXJlZCArIDEKICAgICAgICAgICAgeCwgeSA9IG54LCBueQogICAgICAgIHJldHVybiBUcnVlCgogICAgZGVmIFByaW50U29sdXRpb24oc2VsZik6CiAgICAgICAgbWF4RGlnaXRzID0gbGVuKHN0cihzZWxmLl9fblNxdWFyZWQpKQogICAgICAgIGZvciByb3cgaW4gc2VsZi5fX29yZGVyVGFibGU6CiAgICAgICAgICAgIHByaW50KCcgJy5qb2luKChmJ3t2YWx1ZTp7bWF4RGlnaXRzfX0nIGZvciB2YWx1ZSBpbiByb3cpKSkKCiAgICBkZWYgX19OZXh0RnJvbShzZWxmLCB4LCB5LCBwaWNrTW92ZUNhbmRpZGF0ZXNJblJhbmRvbU9yZGVyOiBib29sKToKICAgICAgICBueCwgbnkgPSAtMSwgLTEKICAgICAgICBtaW5EZWdyZWUgPSBsZW4oc2VsZi5fX3Bvc3NpYmxlTW92ZXMpICsgMQoKICAgICAgICB2YWxpZERlc3RpbmF0aW9ucyA9IHNlbGYuX19WYWxpZERlc3RpbmF0aW9uc0Zyb20oeCwgeSkKICAgICAgICBpZiBwaWNrTW92ZUNhbmRpZGF0ZXNJblJhbmRvbU9yZGVyOgogICAgICAgICAgICB2YWxpZERlc3RpbmF0aW9ucyA9IHNlbGYuX19WYWxpZERlc3RpbmF0aW9uc0luUmFuZG9tT3JkZXJGcm9tKHgsIHkpCgogICAgICAgIGZvciBkc3RYLCBkc3RZIGluIHZhbGlkRGVzdGluYXRpb25zOgogICAgICAgICAgICBkc3REZWdyZWUgPSBzZWxmLl9fQ2FsY3VsYXRlRGVncmVlT2YoZHN0WCwgZHN0WSkKICAgICAgICAgICAgaWYgZHN0RGVncmVlIDwgbWluRGVncmVlOgogICAgICAgICAgICAgICAgbWluRGVncmVlID0gZHN0RGVncmVlCiAgICAgICAgICAgICAgICBueCwgbnkgPSBkc3RYLCBkc3RZCiAgICAgICAgcmV0dXJuIG54LCBueQoKICAgIGRlZiBfX0luQm91bmRzKHNlbGYsIHgsIHkpOgogICAgICAgIHJldHVybiAwIDw9IHggPCBzZWxmLl9fbiBhbmQgMCA8PSB5IDwgc2VsZi5fX24KCiAgICBkZWYgX19WYWxpZERlc3RpbmF0aW9uc0Zyb20oc2VsZiwgeCwgeSk6CiAgICAgICAgZm9yIGR4LCBkeSBpbiBzZWxmLl9fcG9zc2libGVNb3ZlczoKICAgICAgICAgICAgbngsIG55ID0geCArIGR4LCB5ICsgZHkKICAgICAgICAgICAgaWYgc2VsZi5fX0luQm91bmRzKG54LCBueSkgYW5kIHNlbGYuX19vcmRlclRhYmxlW255XVtueF0gPT0gMDoKICAgICAgICAgICAgICAgIHlpZWxkIG54LCBueQoKICAgIGRlZiBfX1ZhbGlkRGVzdGluYXRpb25zSW5SYW5kb21PcmRlckZyb20oc2VsZiwgeCwgeSk6CiAgICAgICAgdmFsaWREZXN0aW5hdGlvbnMgPSBsaXN0KHNlbGYuX19WYWxpZERlc3RpbmF0aW9uc0Zyb20oeCwgeSkpCiAgICAgICAgcmFuZG9tLnNodWZmbGUodmFsaWREZXN0aW5hdGlvbnMpCiAgICAgICAgcmV0dXJuIHZhbGlkRGVzdGluYXRpb25zCgogICAgZGVmIF9fQ2FsY3VsYXRlRGVncmVlT2Yoc2VsZiwgeCwgeSk6CiAgICAgICAgcmV0dXJuIHN1bSgxIGZvciBkc3QgaW4gc2VsZi5fX1ZhbGlkRGVzdGluYXRpb25zRnJvbSh4LCB5KSkKCgppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgbiA9IDEwCiAgICBtb3ZlcyA9IFsoMywgMCksICgyLCAyKSwgKDAsIDMpLCAoLTIsIDIpLCAoLTMsIDApLCAoLTIsIC0yKSwgKDAsIC0zKSwgKDIsIC0yKV0KICAgICMgbW92ZXMgPSBbKDEsIDApLCAoMCwgMSksICgtMSwgMCksICgwLCAtMSldCiAgICBzb2x2ZXIgPSBTb2x2ZXIobiwgbW92ZXMpCgogICAgd2hpbGUgVHJ1ZToKICAgICAgICBpZiBzb2x2ZXIuU29sdmUoc3RhcnRQb3NpdGlvbj0oMCwgMCksIHBpY2tNb3ZlQ2FuZGlkYXRlc0luUmFuZG9tT3JkZXI9RmFsc2UpOgogICAgICAgICAgICBzb2x2ZXIuUHJpbnRTb2x1dGlvbigpCiAgICAgICAgICAgIGJyZWFrCgogICAgaXRlcmF0aW9uc0NvdW50ID0gMTAwMAogICAgc3VjY2Vzc0NvdW50ID0gc3VtKGludChzb2x2ZXIuU29sdmUocGlja01vdmVDYW5kaWRhdGVzSW5SYW5kb21PcmRlcj1GYWxzZSkpIGZvciBpIGluIHJhbmdlKGl0ZXJhdGlvbnNDb3VudCkpCiAgICBwcmludChmJ1N1Y2Nlc3MgaW4ge3N1Y2Nlc3NDb3VudH0gb2Yge2l0ZXJhdGlvbnNDb3VudH0gaXRlcmF0aW9ucyB3aXRob3V0IHJhbmRvbSBjYW5kaWRhdGVzIG9yZGVyaW5nLicpCiAgICBzdWNjZXNzQ291bnQgPSBzdW0oaW50KHNvbHZlci5Tb2x2ZShwaWNrTW92ZUNhbmRpZGF0ZXNJblJhbmRvbU9yZGVyPVRydWUpKSBmb3IgaSBpbiByYW5nZShpdGVyYXRpb25zQ291bnQpKQogICAgcHJpbnQoZidTdWNjZXNzIGluIHtzdWNjZXNzQ291bnR9IG9mIHtpdGVyYXRpb25zQ291bnR9IGl0ZXJhdGlvbnMgd2l0aCByYW5kb20gY2FuZGlkYXRlcyBvcmRlcmluZy4nKQo=