sudoku.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import sys
  2. import copy
  3. from contextlib import suppress
  4. from heapq import heappush, heappop
  5. board = sys.argv[1]
  6. algotype = sys.argv[2]
  7. # import board
  8. with open(board) as file:
  9. board = file.read().splitlines()
  10. board = board[:-1]
  11. # convert to list of list of ints
  12. for l in board:
  13. board[board.index(l)] = list(map(lambda x: int(x), l.split()))
  14. # return a board that is like the board b, but has domains for each element of b (always 1-9)
  15. def genDomains(b):
  16. for row in range(0, 9):
  17. for cell in range(0, 9):
  18. if (b[row][cell] == 0):
  19. b[row][cell] = list(range(1, 10))
  20. return b
  21. # returns True if value is valid
  22. def valid(brd, row, col, val):
  23. # check row
  24. if (val in brd[row]):
  25. return False
  26. # check column
  27. for i in range(0, 9):
  28. if (brd[i][col] == val):
  29. return False
  30. # check "box"
  31. rownum = int(row / 3)
  32. colnum = int(col / 3)
  33. for i in range(rownum * 3, rownum * 3 + 3):
  34. for j in range(colnum * 3, colnum * 3 + 3):
  35. if (brd[i][j] == val):
  36. return False
  37. return True
  38. # naive backtracking solver
  39. def naive(start):
  40. working = copy.deepcopy(start) # this is only "filled in values" and 0s
  41. solution = genDomains(start)
  42. # unassigned will be a list of positions we have to fill
  43. unassigned = []
  44. for i in range(0, 9):
  45. for j in range(0, 9):
  46. if (isinstance(solution[i][j], list)):
  47. unassigned.append((i, j))
  48. assumptions = []
  49. if(len(unassigned) == 0):
  50. return (working, 0)
  51. # count assignments
  52. count = 0
  53. # while there are unassigned vars, keep going
  54. while(len(unassigned)):
  55. index = unassigned[-1]
  56. success = False
  57. # iterate over all values in the domain list
  58. while solution[index[0]][index[1]]:
  59. i = solution[index[0]][index[1]].pop()
  60. count += 1
  61. # took too long
  62. if (count >= 10000):
  63. print("took too long")
  64. return False
  65. # check if this part of the domain(solution) is valid
  66. if (valid(working, index[0], index[1], i)):
  67. #count += 1
  68. #if (count >= 10000):
  69. # print("took too long")
  70. # return False
  71. solution[index[0]][index[1]].append(i) # keep in the domain
  72. working[index[0]][index[1]] = i
  73. assumptions.append(index)
  74. unassigned.pop()
  75. success = True
  76. break
  77. if (success):
  78. continue
  79. else:
  80. # restore domain to full since we failed
  81. solution[index[0]][index[1]] = list(range(1, 10))
  82. working[index[0]][index[1]] = 0
  83. lastdex = assumptions.pop()
  84. solution[lastdex[0]][lastdex[1]].remove(working[lastdex[0]][lastdex[1]])
  85. working[lastdex[0]][lastdex[1]] = 0
  86. unassigned.append(lastdex)
  87. # if we exit without assigning everything, we should have failed
  88. if (unassigned): return False
  89. return (working, count)
  90. # returns a board (domains) where inferences are made for the cell at row, col
  91. def infer(solutions, brd, row, col, val):
  92. domains = copy.deepcopy(solutions)
  93. # remove from same row & col
  94. for i in range(0, 9):
  95. if (val in domains[row][i] and i != col):
  96. domains[row][i].remove(val)
  97. if (val in domains[i][col] and i != row):
  98. domains[i][col].remove(val)
  99. # remove for "box"
  100. rownum = int(row / 3)
  101. colnum = int(col / 3)
  102. for i in range(rownum * 3, rownum * 3 + 3):
  103. for j in range(colnum * 3, colnum * 3 + 3):
  104. if (val in domains[i][j] and (i != row and j != col)):
  105. domains[i][j].remove(val)
  106. return domains
  107. # generates domains in a format supporting forward checking
  108. def gen2Domains(b):
  109. for row in range(0, 9):
  110. for cell in range(0, 9):
  111. if (b[row][cell] == 0):
  112. b[row][cell] = list(range(1, 10))
  113. else:
  114. b[row][cell] = [b[row][cell]]
  115. return b
  116. # recursive solver for forward-checking
  117. def solve(working, domains, unassigned, count):
  118. if (not unassigned):
  119. return (working, count)
  120. index = unassigned.pop()
  121. # for every value in the domain, check if using it works. if all fail, backtrack.
  122. for i in domains[index[0]][index[1]]:
  123. working[index[0]][index[1]] = i
  124. newdomains = infer(domains, working, index[0], index[1], i)
  125. count += 1
  126. # took too long
  127. if (count >= 10000):
  128. print("took too long")
  129. return False
  130. result = solve(working, newdomains, copy.deepcopy(unassigned), count)
  131. if (result):
  132. return result
  133. else:
  134. continue
  135. return False
  136. # forward checking solver
  137. def forward(start):
  138. working = copy.deepcopy(start) # this is only "filled in values" and 0s
  139. domains = gen2Domains(start)
  140. # unassigned will be a list of positions we have to fill
  141. unassigned = []
  142. for i in range(0, 9):
  143. for j in range(0, 9):
  144. if (len(domains[i][j]) == 9):
  145. unassigned.append((i, j))
  146. # forward-checking on pre-assigned values
  147. for i in range(0, 9):
  148. for j in range(0, 9):
  149. if (working[i][j] != 0):
  150. domains = infer(domains, working, i, j, working[i][j])
  151. return solve(working, domains, unassigned, 0)
  152. # returns size of domain for a given index
  153. def domsize(domains, index):
  154. return (len(domains[index[0]][index[1]]))
  155. # returns the # of 0s that are in the same row, col, or box as index
  156. def related(brd, index):
  157. count = 0
  158. # count 0s in row + col
  159. for i in range(0, 9):
  160. if (brd[index[0]][i] == 0 and i != index[1]):
  161. ++count
  162. if (brd[i][index[1]] == 0 and i != index[0]):
  163. ++count
  164. # count for "box" as well
  165. rownum = int(index[0] / 3)
  166. colnum = int(index[1] / 3)
  167. for i in range(rownum * 3, rownum * 3 + 3):
  168. for j in range(colnum * 3, colnum * 3 + 3):
  169. if (brd[i][j] == 0 and (i != index[0] and j != index[1])):
  170. ++count
  171. return count
  172. # returns the # of constraints that will follow from assigning index with val
  173. def lcv(solutions, index, val):
  174. count = 0
  175. # count 0s in row + col
  176. for i in range(0, 9):
  177. if (val in solutions[index[0]][i] and i != index[1]):
  178. ++count
  179. if (val in solutions[i][index[1]] and i != index[0]):
  180. ++count
  181. # count for "box" as well
  182. rownum = int(index[0] / 3)
  183. colnum = int(index[1] / 3)
  184. for i in range(rownum * 3, rownum * 3 + 3):
  185. for j in range(colnum * 3, colnum * 3 + 3):
  186. if (val in solutions[i][j] and (i != index[0] and j != index[1])):
  187. ++count
  188. return count
  189. # return the correct node + val to try
  190. def genVal(domains, working, unassigned):
  191. # used to track intermediary values
  192. heap = []
  193. superheap = []
  194. bestrating = 1.0
  195. # get the best indices according to domain size
  196. for i in unassigned:
  197. rating = domsize(domains, i) / 9.0
  198. if (rating < bestrating):
  199. bestrating = rating
  200. heap = [i]
  201. elif (rating == bestrating):
  202. heap.append(i)
  203. # get the best indices according to degree(related cells)
  204. bestrating = 1
  205. for i in heap:
  206. rating = related(working, i) / 27.0
  207. if (rating < bestrating):
  208. bestrating = rating
  209. superheap = [i]
  210. elif (rating == bestrating):
  211. superheap.append(i)
  212. index = superheap[0]
  213. bestrating = 27
  214. val = working[index[0]][index[1]]
  215. # get best values according to LCV
  216. for i in domains[index[0]][index[1]]:
  217. rating = lcv(domains, index, i)
  218. if (rating <= bestrating):
  219. bestrating = rating
  220. val = i
  221. return (index, val)
  222. # recursive solver that uses heuristics to decide what node to explore
  223. def solveh(working, domains, unassigned, count):
  224. if (not unassigned):
  225. return (working, count)
  226. # while there are unassigned values keep trying
  227. while(unassigned):
  228. # get next value using heuristics, remove this node from assigned
  229. nextThing = genVal(domains, working, unassigned)
  230. index = nextThing[0]
  231. val = nextThing[1]
  232. working[index[0]][index[1]] = val
  233. unassigned.remove(index)
  234. # check for invalidated nodes (empty domain)
  235. flag = True
  236. result = False
  237. newdomains = infer(domains, working, index[0], index[1], val)
  238. for i in range(0, 9):
  239. for j in range(0, 9):
  240. if (not domains[i][j]):
  241. flag = False
  242. count += 1
  243. # took too long
  244. if (count >= 10000):
  245. print("took too long")
  246. return False
  247. # success! recurse
  248. if (flag): result = solveh(working, newdomains, copy.deepcopy(unassigned), count)
  249. if (result):
  250. return result
  251. elif (len(domains[index[0]][index[1]]) > 1): # remove from domain, keep going
  252. working[index[0]][index[1]] = 0
  253. domains[index[0]][index[1]].remove(val)
  254. unassigned.append(index)
  255. else: # no values worked :( return false
  256. return False
  257. # forward checking solver with heuristics
  258. def heuristic(start):
  259. working = copy.deepcopy(start) # this is only "filled in values" and 0s
  260. domains = gen2Domains(start)
  261. # unassigned will be a list of positions we have to fill
  262. unassigned = []
  263. for i in range(0, 9):
  264. for j in range(0, 9):
  265. if (len(domains[i][j]) == 9):
  266. unassigned.append((i, j))
  267. # initial inferences
  268. for i in range(0, 9):
  269. for j in range(0, 9):
  270. if (working[i][j] != 0):
  271. domains = infer(domains, working, i, j, working[i][j])
  272. return solveh(working, domains, unassigned, 0)
  273. def main():
  274. print("###########")
  275. print(*board, sep='\n')
  276. print("##########")
  277. if (algotype == str(0)):
  278. result = naive(board)
  279. elif (algotype == str(1)):
  280. result = forward(board)
  281. elif (algotype == str(2)):
  282. result = heuristic(board)
  283. else:
  284. print("No valid algorithm selected. RIP.")
  285. if (not result or not result[0]):
  286. print("No board to print")
  287. return
  288. print("count: ", result[1])
  289. print("###########")
  290. print(*result[0], sep='\n')
  291. print("##########")
  292. main()