Advertisement
Guest User

Untitled

a guest
Mar 4th, 2017
134
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.01 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import numpy as np
  4.  
  5.  
  6. class Sudoku():
  7. """Class for handling 9x9 sudokus.
  8.  
  9. Args:
  10. input_grid (str): String with the path to a 9 line text
  11. file with 9 numbers where gaps are
  12. represented by '0'
  13. input grid (nda): 9 by 9 numpy array with gaps set as '0'
  14.  
  15. Attributes:
  16. grid (nda): current state of the sudoku grid
  17. length (int): length of the grid, for now always 9
  18. sub_length (int): sqrt of the length
  19. check_sum (int): sum of a unique row/column
  20. bu_grid (lst): list of backup grids used for guessing
  21. guesses (int): number of guesses for the current grid
  22. max_guesses (int): maximal number of guesses for the current grid
  23. mask (nda): 9x9x9 numpy array (bit mask).
  24.  
  25. Example:
  26. s = Sudoku(np.array(
  27. [[5, 3, 0, 0, 7, 0, 0, 0, 0],
  28. [6, 0, 0, 1, 9, 5, 0, 0, 0],
  29. [0, 9, 8, 0, 0, 0, 0, 6, 0],
  30. [8, 0, 0, 0, 6, 0, 0, 0, 3],
  31. [4, 0, 0, 8, 0, 3, 0, 0, 1],
  32. [7, 0, 0, 0, 2, 0, 0, 0, 6],
  33. [0, 6, 0, 0, 0, 0, 2, 8, 0],
  34. [0, 0, 0, 4, 1, 9, 0, 0, 5],
  35. [0, 0, 0, 0, 8, 0, 0, 7, 9]]))
  36. s.solve()
  37. s.print_grid()
  38. """
  39.  
  40. def __init__(self, input_grid):
  41. """Initialize an instance of the Sudoku class.
  42.  
  43. Set up the initial gird based on text or numpy array.
  44. Next get the length of one of the axis as well as the
  45. sub length. The check sum is used to validate the grid
  46. based on unique sum of all digits. The list of back up
  47. grids is initialized as an empty list together with
  48. the current guess and the current number of maximal
  49. guesses. Last the 'bit' mask is set to all zeros.
  50. """
  51. self.grid = self.read_grid(input_grid)
  52. self.length = int(np.sqrt(np.size(self.grid)))
  53. self.sub_length = int(np.sqrt(self.length))
  54. self.check_sum = int(np.sum(range(self.length + 1)))
  55. self.bu_grid = []
  56. self.guesses = 0
  57. self.max_guesses = 2
  58. self.mask = np.zeros([self.length, self.length, self.length])
  59.  
  60. def __repr__(self):
  61. self.print_grid()
  62.  
  63. def read_grid(self, input_grid):
  64. """Parse the sudoku grid.
  65.  
  66. Read a text file containing 9 lines with 9 non-spaced digits
  67. and converts it to numpy array. Blank spaces are represented
  68. by 0. Alternatively takes a already formatted numpy array as
  69. input and returns the input as a 9x9 array.
  70. """
  71. if isinstance(input_grid, str):
  72. grid = []
  73. with open(input_grid) as f:
  74. text = f.read()
  75. for line in text.split():
  76. grid.append([int(element) for element in line])
  77. return np.array(grid)
  78. elif isinstance(input_grid, np.ndarray):
  79. return input_grid
  80. else:
  81. raise ValueError('Not a valid input grid')
  82.  
  83. def backup_grid(self):
  84. """Create a list of back up grids.
  85.  
  86. Create a back up of of the current grid and guess number to
  87. have a restore point in case a guess turns out wrong.
  88. """
  89. bckup = BackUpGrid(self.grid, self.guesses)
  90. self.bu_grid.append(bckup)
  91.  
  92. def restore_grid(self):
  93. """Restore the last backup grid.
  94.  
  95. If a back up grid is present overwrite the current grid,
  96. and increase the guess count by one.
  97. """
  98. if not self.bu_grid:
  99. raise ValueError('No backup grid')
  100. else:
  101. self.bu_grid[-1].guesses += 1
  102. bckup = self.bu_grid[-1]
  103. self.grid = bckup.grid
  104. self.guesses = bckup.guesses
  105.  
  106. def print_grid(self):
  107. """Print formatted grid."""
  108. BOLD = '33[1m'
  109. ENDC = '33[0m'
  110.  
  111. fill_mask = np.sum(self.mask, axis=2) == 1
  112.  
  113. top_line = '┏' + ('━━┯' * 2 + '━━┳') * 2 + '━━┯' * 2 + '━━┓'
  114. mid_line = '┣' + ('━━┿' * 2 + '━━╋') * 2 + '━━┿' * 2 + '━━┫'
  115. bot_line = '┗' + ('━━┷' * 2 + '━━┻') * 2 + '━━┷' * 2 + '━━┛'
  116. div_line = '┠' + ('──┼' * 2 + '──╂') * 2 + '──┼' * 2 + '──┨'
  117. num_line = '┃ %s│ %s│ %s' * 3 + '┃'
  118.  
  119. counter = 1
  120. print(top_line)
  121. for row_index, row in enumerate(self.grid):
  122. line_to_print = []
  123. for col_index, cell in enumerate(row):
  124. if fill_mask[row_index, col_index] == 1:
  125. line_to_print.append(BOLD + str(cell) + ENDC)
  126. elif cell == 0:
  127. line_to_print.append(' ')
  128. else:
  129. line_to_print.append(str(cell))
  130. print(num_line % tuple(line_to_print))
  131. if (counter % 3 == 0) and counter < 9:
  132. print(mid_line)
  133. elif counter < 9:
  134. print(div_line)
  135. counter += 1
  136. print(bot_line)
  137.  
  138. def fill_grid(self):
  139. """Fill the sudoku grid based on the mask.
  140.  
  141. Looks for all the points where the third axis has only one
  142. option and sets the corresponding number in the grid.
  143. """
  144. unique_mask = self.mask.sum(axis=2) == 1
  145. for i in range(self.length):
  146. value_mask = self.mask[:, :, i] != 0
  147. self.grid[np.logical_and(unique_mask, value_mask)] = i + 1
  148.  
  149. sub_slice_index = np.arange(0, self.length + 1, self.sub_length)
  150. for k in range(self.sub_length):
  151. for l in range(self.sub_length):
  152. sub_mask =
  153. self.mask[sub_slice_index[k]:sub_slice_index[k+1],
  154. sub_slice_index[l]:sub_slice_index[l+1], i]
  155. if np.sum(sub_mask) == 1:
  156. possition = np.where(sub_mask)
  157. self.grid[sub_slice_index[k] + possition[0],
  158. sub_slice_index[l] + possition[1]] = i + 1
  159.  
  160. def update_mask(self):
  161. """Solve the bit mask for each cell."""
  162. # Create initial mask and remove already filled points
  163. mask = np.dstack([self.grid == 0] * self.length)
  164. sub_slice_index = np.arange(0, self.length + 1, self.sub_length)
  165. full_slice = np.arange(self.length)
  166. for i in range(self.length): # Loop trough numbers to fill
  167. for j in range(self.length): # Loop trough columns/rows
  168. # Check for horizontal and vertical matches
  169. if i + 1 in self.grid[j, :]:
  170. mask[j, :, i] = False
  171. if i + 1 in self.grid[:, j]:
  172. mask[:, j, i] = False
  173. # Check the 3x3 blocks
  174. for k in range(self.sub_length):
  175. for l in range(self.sub_length):
  176. # Get the 3x3 grid and mask
  177. sub_slice =
  178. mask[sub_slice_index[k]:sub_slice_index[k+1],
  179. sub_slice_index[l]:sub_slice_index[l+1], i]
  180. sub_grid =
  181. self.grid[sub_slice_index[k]:sub_slice_index[k+1],
  182. sub_slice_index[l]:sub_slice_index[l+1]]
  183. # If value is in 3x3 block set mask for entire block
  184. # to false
  185. if i + 1 in sub_grid:
  186. mask[sub_slice_index[k]:sub_slice_index[k+1],
  187. sub_slice_index[l]:sub_slice_index[l+1], i]
  188. = False
  189. # If only one value exists in the 3th axis set all others
  190. # in the sub slice to False
  191. elif np.sum(sub_slice) == 1:
  192. possition = np.where(sub_slice)
  193. cut_slice = np.delete(full_slice, i)
  194. mask[sub_slice_index[k] + possition[0],
  195. sub_slice_index[l] + possition[1],
  196. cut_slice] = False
  197. # Check if position in 3x3 block eliminates some
  198. # horizontal or vertical options
  199. if (1 < np.sum(sub_slice) <= self.sub_length):
  200. possition = np.where(sub_slice)
  201. check_horizontal = possition[0][0] == possition[0]
  202. check_vertical = possition[1][0] == possition[1]
  203. if check_horizontal.all():
  204. keep = sub_slice_index[l] + possition[1]
  205. cut_slice = np.delete(full_slice, keep)
  206. mask[possition[0][0] + sub_slice_index[k],
  207. cut_slice, i] = False
  208. elif check_vertical.all():
  209. keep = sub_slice_index[k] + possition[0]
  210. cut_slice = np.delete(full_slice, keep)
  211. mask[cut_slice, possition[1][0] +
  212. sub_slice_index[l], i] = False
  213. self.mask = mask
  214.  
  215. def solve(self, max_itter=150, grid_out=False, verbose=False,
  216. user_controle=False):
  217. """Main solver routine to solve a 9x9 sudoku."""
  218. count = 0
  219. if verbose:
  220. self.print_grid()
  221. while count < max_itter:
  222. is_valid = self.is_valid()
  223. solved = self.is_solved()
  224. if solved and is_valid:
  225. if verbose:
  226. print('Grid solved')
  227. if grid_out:
  228. return True, self.grid
  229. else:
  230. return True
  231. self.update_mask()
  232. if ((np.sum(self.mask, axis=2) == 1).any() and is_valid):
  233. if verbose:
  234. print('Filling %i cells' %
  235. int(np.sum(np.sum(self.mask, axis=2) == 1)))
  236. self.fill_grid()
  237. elif ((np.sum(self.mask, axis=2) > 1).any() and is_valid):
  238. if verbose:
  239. print('Valid grid but nothing to fill. Guessing...')
  240. self.guesses = 0
  241. self.backup_grid()
  242. self.guess()
  243. self.fill_grid()
  244. else:
  245. if verbose:
  246. print('Guessed wrong, resetting grid')
  247. self.restore_grid()
  248. self.update_mask()
  249. self.guess()
  250. self.fill_grid()
  251. if self.guesses == (self.max_guesses - 1):
  252. self.bu_grid.pop(-1)
  253.  
  254. count += 1
  255. if verbose:
  256. print('On loop: %02i' % count)
  257. self.print_grid()
  258. if user_controle:
  259. inp = input("'' --> Nextn'e' --> Go to endn'q' --> Quitn")
  260. if inp is 'e':
  261. user_controle = False
  262. elif inp is 'q':
  263. break
  264. return False
  265.  
  266. def guess(self):
  267. """Guessing routine.
  268.  
  269. Start by looking for the position where only two options are
  270. possible. For this position guess the first option by
  271. setting the second option to false in the mask. If there are no
  272. two option cells increase to 3 options etc.
  273. """
  274. n_options = 2
  275. while n_options < 9:
  276. position = np.where(self.mask.sum(axis=2) == n_options)
  277. if position[0].any():
  278. self.max_guesses = n_options
  279. x = position[0][0]
  280. y = position[1][0]
  281. options = np.arange(self.length)[self.mask[x, y, :]]
  282. options = np.delete(options, self.guesses)
  283. self.mask[x, y, options] = False
  284. return
  285. else:
  286. n_options += 1
  287.  
  288. def is_valid(self):
  289. """Check for duplicates in sudoku grid."""
  290. for i in range(self.length):
  291. vertical = self.grid[:, i].tolist()
  292. horizontal = self.grid[i, :].tolist()
  293. for j in range(self.length):
  294. try:
  295. vertical.remove(j + 1)
  296. except ValueError:
  297. pass
  298. try:
  299. horizontal.remove(j + 1)
  300. except:
  301. pass
  302. if (np.sum(vertical) or np.sum(horizontal)) > 0:
  303. return False
  304. return True
  305.  
  306. def is_solved(self):
  307. """Check if the gird is fully solved"""
  308. for i in range(self.length):
  309. if int(np.sum(self.grid[i, :])) != self.check_sum:
  310. return False
  311. if int(np.sum(self.grid[:, i])) != self.check_sum:
  312. return False
  313. return True
  314.  
  315.  
  316. class BackUpGrid():
  317. """Back up class.
  318.  
  319. Minimal class to store both the grid as well as the current
  320. number of guesses.
  321. """
  322.  
  323. def __init__(self, grid, guesses):
  324. self.grid = np.array(grid)
  325. self.guesses = guesses
  326.  
  327. import numpy as np
  328.  
  329.  
  330. s = Sudoku(np.array(
  331. [[5, 3, 0, 0, 7, 0, 0, 0, 0],
  332. [6, 0, 0, 1, 9, 5, 0, 0, 0],
  333. [0, 9, 8, 0, 0, 0, 0, 6, 0],
  334. [8, 0, 0, 0, 6, 0, 0, 0, 3],
  335. [4, 0, 0, 8, 0, 3, 0, 0, 1],
  336. [7, 0, 0, 0, 2, 0, 0, 0, 6],
  337. [0, 6, 0, 0, 0, 0, 2, 8, 0],
  338. [0, 0, 0, 4, 1, 9, 0, 0, 5],
  339. [0, 0, 0, 0, 8, 0, 0, 7, 9]]))
  340. s.solve(verbose=True, user_controle=True)
  341. s.print_grid()
  342.  
  343. import numpy as np
  344.  
  345. answer = []
  346. with open('sudoku.txt', 'r') as file:
  347. number = 0
  348. for line in file:
  349. if 'Grid' in line:
  350. counter = 0
  351. grid = []
  352. elif counter < 9:
  353. grid.append([int(x) for x in line.strip()])
  354. counter += 1
  355. if counter == 9:
  356. sudoku_to_solve = Sudoku(np.array(grid))
  357. answer.append(sudoku_to_solve.solve())
  358. number += 1
  359. print('%i: %r' % (number, answer[-1]))
  360.  
  361. with open(input_grid) as f:
  362. text = f.read()
  363. for line in text.split():
  364. grid.append([int(element) for element in line])
  365.  
  366. with open(input_grid) as f:
  367. for line in f:
  368. grid.append([int(e) for e in line.split()])
  369.  
  370. if not self.bu_grid:
  371. raise ValueError('No backup grid')
  372. else:
  373. self.bu_grid[-1].guesses += 1
  374. bckup = self.bu_grid[-1]
  375. self.grid = bckup.grid
  376. self.guesses = bckup.guesses
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement