Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3156956

Browse files
author
unknown
committed
Added a non-recursive implementation of conjoin(), and a Knight's Tour
solver. In conjunction, they easily found a tour of a 200x200 board: that's 200**2 == 40,000 levels of backtracking. Explicitly resumable generators allow that to be coded as easily as a recursive solver (easier, actually, because different levels can use level-customized algorithms without pain), but without blowing the stack. Indeed, I've never written an exhaustive Tour solver in any language before that can handle boards so large ("exhaustive" == guaranteed to find a solution if one exists, as opposed to probabilistic heuristic approaches; of course, the age of the universe may be a blip in the time needed!).
1 parent a5aa0b5 commit 3156956

1 file changed

Lines changed: 291 additions & 2 deletions

File tree

Lib/test/test_generators.py

Lines changed: 291 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,42 @@ def _gen3(i, values=values):
909909
for x in gen(0):
910910
yield x
911911

912+
# And one more approach: For backtracking apps like the Knight's Tour
913+
# solver below, the number of backtracking levels can be enormous (one
914+
# level per square, for the Knight's Tour, so that e.g. a 100x100 board
915+
# needs 10,000 levels). In such cases Python is likely to run out of
916+
# stack space due to recursion. So here's a recursion-free version of
917+
# conjoin too.
918+
# NOTE WELL: This allows large problems to be solved with only trivial
919+
# demands on stack space. Without explicitly resumable generators, this is
920+
# much harder to achieve.
921+
922+
def flat_conjoin(gs): # rename to conjoin to run tests with this instead
923+
n = len(gs)
924+
values = [None] * n
925+
iters = [None] * n
926+
i = 0
927+
while i >= 0:
928+
# Need a fresh iterator.
929+
if i >= n:
930+
yield values
931+
# Backtrack.
932+
i -= 1
933+
else:
934+
iters[i] = gs[i]().next
935+
936+
# Need next value from current iterator.
937+
while i >= 0:
938+
try:
939+
values[i] = iters[i]()
940+
except StopIteration:
941+
# Backtrack.
942+
i -= 1
943+
else:
944+
# Start fresh at next level.
945+
i += 1
946+
break
947+
912948
# A conjoin-based N-Queens solver.
913949

914950
class Queens:
@@ -961,12 +997,207 @@ def printsolution(self, row2col):
961997
print "|" + "|".join(squares) + "|"
962998
print sep
963999

1000+
# A conjoin-based Knight's Tour solver. This is pretty sophisticated
1001+
# (e.g., when used with flat_conjoin above, and passing hard=1 to the
1002+
# constructor, a 200x200 Knight's Tour was found quickly -- note that we're
1003+
# creating 10s of thousands of generators then!), so goes on at some length
1004+
1005+
class Knights:
1006+
def __init__(self, n, hard=0):
1007+
self.n = n
1008+
1009+
def coords2index(i, j):
1010+
return i*n + j
1011+
1012+
offsets = [( 1, 2), ( 2, 1), ( 2, -1), ( 1, -2),
1013+
(-1, -2), (-2, -1), (-2, 1), (-1, 2)]
1014+
succs = []
1015+
for i in range(n):
1016+
for j in range(n):
1017+
s = [coords2index(i+io, j+jo) for io, jo in offsets
1018+
if 0 <= i+io < n and
1019+
0 <= j+jo < n]
1020+
succs.append(s)
1021+
del s
1022+
del offsets
1023+
1024+
free = [0] * n**2 # 0 if occupied, 1 if visited
1025+
nexits = free[:] # number of free successors
1026+
1027+
def decrexits(i0):
1028+
# If we remove all exits from a free square, we're dead:
1029+
# even if we move to it next, we can't leave it again.
1030+
# If we create a square with one exit, we must visit it next;
1031+
# else somebody else will have to visit it, and since there's
1032+
# only one adjacent, there won't be a way to leave it again.
1033+
# Finelly, if we create more than one free square with a
1034+
# single exit, we can only move to one of them next, leaving
1035+
# the other one a dead end.
1036+
ne0 = ne1 = 0
1037+
for i in succs[i0]:
1038+
if free[i]:
1039+
e = nexits[i] - 1
1040+
nexits[i] = e
1041+
if e == 0:
1042+
ne0 += 1
1043+
elif e == 1:
1044+
ne1 += 1
1045+
return ne0 == 0 and ne1 < 2
1046+
1047+
def increxits(i0):
1048+
for i in succs[i0]:
1049+
if free[i]:
1050+
nexits[i] += 1
1051+
1052+
# Generate the first move.
1053+
def first():
1054+
if n < 1:
1055+
return
1056+
1057+
# Initialize board structures.
1058+
for i in xrange(n**2):
1059+
free[i] = 1
1060+
nexits[i] = len(succs[i])
1061+
1062+
# Since we're looking for a cycle, it doesn't matter where we
1063+
# start. Starting in a corner makes the 2nd move easy.
1064+
corner = coords2index(0, 0)
1065+
free[corner] = 0
1066+
decrexits(corner)
1067+
self.lastij = corner
1068+
yield corner
1069+
increxits(corner)
1070+
free[corner] = 1
1071+
1072+
# Generate the second moves.
1073+
def second():
1074+
corner = coords2index(0, 0)
1075+
assert self.lastij == corner # i.e., we started in the corner
1076+
if n < 3:
1077+
return
1078+
assert nexits[corner] == len(succs[corner]) == 2
1079+
assert coords2index(1, 2) in succs[corner]
1080+
assert coords2index(2, 1) in succs[corner]
1081+
# Only two choices. Whichever we pick, the other must be the
1082+
# square picked on move n**2, as it's the only way to get back
1083+
# to (0, 0). Save its index in self.final so that moves before
1084+
# the last know it must be kept free.
1085+
for i, j in (1, 2), (2, 1):
1086+
this, final = coords2index(i, j), coords2index(3-i, 3-j)
1087+
assert free[this] and free[final]
1088+
self.final = final
1089+
nexits[final] += 1 # it has an exit back to 0,0
1090+
1091+
free[this] = 0
1092+
decrexits(this)
1093+
self.lastij = this
1094+
yield this
1095+
increxits(this)
1096+
free[this] = 1
1097+
1098+
nexits[final] -= 1
1099+
1100+
# Generate moves 3 thru n**2-1.
1101+
def advance():
1102+
# If some successor has only one exit, must take it.
1103+
# Else favor successors with fewer exits.
1104+
candidates = []
1105+
for i in succs[self.lastij]:
1106+
if free[i]:
1107+
e = nexits[i]
1108+
assert e > 0, "else decrexits() pruning flawed"
1109+
if e == 1:
1110+
candidates = [(e, i)]
1111+
break
1112+
candidates.append((e, i))
1113+
else:
1114+
candidates.sort()
1115+
1116+
for e, i in candidates:
1117+
if i != self.final:
1118+
if decrexits(i):
1119+
free[i] = 0
1120+
self.lastij = i
1121+
yield i
1122+
free[i] = 1
1123+
increxits(i)
1124+
1125+
# Generate moves 3 thru n**2-1. Alternative version using a
1126+
# stronger (but more expensive) heuristic to order successors.
1127+
# Since the # of backtracking levels is n**2, a poor move early on
1128+
# can take eons to undo. Smallest n for which this matters a lot is
1129+
# n==52.
1130+
def advance_hard(midpoint=(n-1)/2.0):
1131+
# If some successor has only one exit, must take it.
1132+
# Else favor successors with fewer exits.
1133+
# Break ties via max distance from board centerpoint (favor
1134+
# corners and edges whenever possible).
1135+
candidates = []
1136+
for i in succs[self.lastij]:
1137+
if free[i]:
1138+
e = nexits[i]
1139+
assert e > 0, "else decrexits() pruning flawed"
1140+
if e == 1:
1141+
candidates = [(e, 0, i)]
1142+
break
1143+
i1, j1 = divmod(i, n)
1144+
d = (i1 - midpoint)**2 + (j1 - midpoint)**2
1145+
candidates.append((e, -d, i))
1146+
else:
1147+
candidates.sort()
1148+
1149+
for e, d, i in candidates:
1150+
if i != self.final:
1151+
if decrexits(i):
1152+
free[i] = 0
1153+
self.lastij = i
1154+
yield i
1155+
free[i] = 1
1156+
increxits(i)
1157+
1158+
# Generate the last move.
1159+
def last():
1160+
assert self.final in succs[self.lastij]
1161+
yield self.final
1162+
1163+
if n <= 1:
1164+
self.rowgenerators = [first]
1165+
else:
1166+
self.rowgenerators = [first, second] + \
1167+
[hard and advance_hard or advance] * (n**2 - 3) + \
1168+
[last]
1169+
1170+
# Generate solutions.
1171+
def solve(self):
1172+
for x in conjoin(self.rowgenerators):
1173+
yield x
1174+
1175+
def printsolution(self, x):
1176+
n = self.n
1177+
assert len(x) == n**2
1178+
w = len(str(n**2 + 1))
1179+
format = "%" + str(w) + "d"
1180+
1181+
squares = [[None] * n for i in range(n)]
1182+
k = 1
1183+
for i in x:
1184+
i1, j1 = divmod(i, n)
1185+
squares[i1][j1] = format % k
1186+
k += 1
1187+
1188+
sep = "+" + ("-" * w + "+") * n
1189+
print sep
1190+
for i in range(n):
1191+
row = squares[i]
1192+
print "|" + "|".join(row) + "|"
1193+
print sep
1194+
9641195
conjoin_tests = """
9651196
9661197
Generate the 3-bit binary numbers in order. This illustrates dumbest-
9671198
possible use of conjoin, just to generate the full cross-product.
9681199
969-
>>> for c in conjoin([lambda: (0, 1)] * 3):
1200+
>>> for c in conjoin([lambda: iter((0, 1))] * 3):
9701201
... print c
9711202
[0, 0, 0]
9721203
[0, 0, 1]
@@ -986,7 +1217,7 @@ def printsolution(self, row2col):
9861217
... yield x[:]
9871218
9881219
>>> for n in range(10):
989-
... all = list(gencopy(conjoin([lambda: (0, 1)] * n)))
1220+
... all = list(gencopy(conjoin([lambda: iter((0, 1))] * n)))
9901221
... print n, len(all), all[0] == [0] * n, all[-1] == [1] * n
9911222
0 1 1 1
9921223
1 2 1 1
@@ -1048,6 +1279,64 @@ def printsolution(self, row2col):
10481279
10491280
>>> print count, "solutions in all."
10501281
92 solutions in all.
1282+
1283+
And run a Knight's Tour on a 10x10 board. Note that there are about
1284+
20,000 solutions even on a 6x6 board, so don't dare run this to exhaustion.
1285+
1286+
>>> k = Knights(10)
1287+
>>> LIMIT = 2
1288+
>>> count = 0
1289+
>>> for x in k.solve():
1290+
... count += 1
1291+
... if count <= LIMIT:
1292+
... print "Solution", count
1293+
... k.printsolution(x)
1294+
... else:
1295+
... break
1296+
Solution 1
1297+
+---+---+---+---+---+---+---+---+---+---+
1298+
| 1| 58| 27| 34| 3| 40| 29| 10| 5| 8|
1299+
+---+---+---+---+---+---+---+---+---+---+
1300+
| 26| 35| 2| 57| 28| 33| 4| 7| 30| 11|
1301+
+---+---+---+---+---+---+---+---+---+---+
1302+
| 59|100| 73| 36| 41| 56| 39| 32| 9| 6|
1303+
+---+---+---+---+---+---+---+---+---+---+
1304+
| 74| 25| 60| 55| 72| 37| 42| 49| 12| 31|
1305+
+---+---+---+---+---+---+---+---+---+---+
1306+
| 61| 86| 99| 76| 63| 52| 47| 38| 43| 50|
1307+
+---+---+---+---+---+---+---+---+---+---+
1308+
| 24| 75| 62| 85| 54| 71| 64| 51| 48| 13|
1309+
+---+---+---+---+---+---+---+---+---+---+
1310+
| 87| 98| 91| 80| 77| 84| 53| 46| 65| 44|
1311+
+---+---+---+---+---+---+---+---+---+---+
1312+
| 90| 23| 88| 95| 70| 79| 68| 83| 14| 17|
1313+
+---+---+---+---+---+---+---+---+---+---+
1314+
| 97| 92| 21| 78| 81| 94| 19| 16| 45| 66|
1315+
+---+---+---+---+---+---+---+---+---+---+
1316+
| 22| 89| 96| 93| 20| 69| 82| 67| 18| 15|
1317+
+---+---+---+---+---+---+---+---+---+---+
1318+
Solution 2
1319+
+---+---+---+---+---+---+---+---+---+---+
1320+
| 1| 58| 27| 34| 3| 40| 29| 10| 5| 8|
1321+
+---+---+---+---+---+---+---+---+---+---+
1322+
| 26| 35| 2| 57| 28| 33| 4| 7| 30| 11|
1323+
+---+---+---+---+---+---+---+---+---+---+
1324+
| 59|100| 73| 36| 41| 56| 39| 32| 9| 6|
1325+
+---+---+---+---+---+---+---+---+---+---+
1326+
| 74| 25| 60| 55| 72| 37| 42| 49| 12| 31|
1327+
+---+---+---+---+---+---+---+---+---+---+
1328+
| 61| 86| 99| 76| 63| 52| 47| 38| 43| 50|
1329+
+---+---+---+---+---+---+---+---+---+---+
1330+
| 24| 75| 62| 85| 54| 71| 64| 51| 48| 13|
1331+
+---+---+---+---+---+---+---+---+---+---+
1332+
| 87| 98| 89| 80| 77| 84| 53| 46| 65| 44|
1333+
+---+---+---+---+---+---+---+---+---+---+
1334+
| 90| 23| 92| 95| 70| 79| 68| 83| 14| 17|
1335+
+---+---+---+---+---+---+---+---+---+---+
1336+
| 97| 88| 21| 78| 81| 94| 19| 16| 45| 66|
1337+
+---+---+---+---+---+---+---+---+---+---+
1338+
| 22| 91| 96| 93| 20| 69| 82| 67| 18| 15|
1339+
+---+---+---+---+---+---+---+---+---+---+
10511340
"""
10521341

10531342
__test__ = {"tut": tutorial_tests,

0 commit comments

Comments
 (0)