Aaron

### Merge branch 'working_1' into 'master'

```Solves easy and medium boards, but not hard yet;

See merge request !1```
Showing with 247 additions and 70 deletions
boards/easy.txt 0 → 100644
 068 000 930 042 000 600 190 080 040 085 201 007 700 890 000 209 007 503 020 100 050 850 040 760 473 052 009 ... ...
boards/hard.txt 0 → 100644
 000 068 030 190 000 000 803 100 200 400 051 060 700 020 004 000 070 800 010 005 007 004 000 000 050 030 100 ... ...
boards/medium.txt 0 → 100644
 250 003 000 000 000 270 870 006 400 020 008 193 015 040 800 000 100 004 000 734 000 000 600 009 064 009 058 ... ...
 import math import random import logging class InvalidBoardSize(Exception): pass class Sudoku(object): def __init__(self, size=9): self.size = size self.board = self.zero_board(self.size) self.board = self.zero_board() self.log = [] def __repr__(self): out = '' sr = int(math.sqrt(self.size)) for x in range(self.size): for y in range(self.size): out = '%s%s ' % (out, self.board[x][y]) ... ... @@ -23,109 +27,242 @@ class Sudoku(object): return out def random_board(self, size=None): if size is None: size = self.size @property def board(self): return self._board for y in range(size): for x in range(size): try: self.board[y][x] = random.choice(self.possible_list(y,x)) except: print(self) print(self.possible_square(y,x)) print(self.possible_row(y)) print(self.possible_col(x)) print(self.possible_list(y, x)) raise Exception('Fail') @board.setter def board(self, value): side_len = len(value) return True root = int(math.sqrt(side_len)) def random_list(self, size=None): if size is None: size = self.size if root**2 != side_len: raise InvalidBoardSize('Board side length must be square number') for row in value: if len(row) != side_len: raise InvalidBoardSize('Board row length must equal board height') self._board = value new_list = list(range(1, size+1)) random.shuffle(new_list) return new_list @property def size(self): return self._size @size.setter def size(self, value): root = int(math.sqrt(value)) if root**2 != value: raise InvalidBoardSize('Board size must be a square number') self._size = value def zero_board(self, size=None): self.board = [] if size is None: size = self.size return [[0 for x in range(size)] for y in range(size)] def possible_list(self, y, x): def load_board_file(self, filename): p_sq = self.possible_square(y, x) p_col = self.possible_col(x) p_row = self.possible_row(y) self.board = [] rm = [] with open(filename, 'r') as f: for z in p_sq: if self.row_has(y, z) or self.col_has(x, z): rm.append(z) for line in f.readlines(): for n in rm: p_sq.remove(n) clean = line.strip() row = [] return p_sq if clean: def possible_row(self, y): for char in clean: try: c = int(char) except: continue possible = self.random_list(self.size) row.append(c) for i in range(self.size): root = int(math.sqrt(len(row))) try: possible.remove(self.board[y][i]) except ValueError: continue if root**2 != len(row): raise Exception('Invalid row length on line %s' % f.line_num) self.board.append(row) return True def save_board_file(self, filename): return possible with open(filename, 'w') as f: f.write(self.__repr__()) def possible_col(self, x): return True possible = self.random_list(self.size) def invert_opts(self, opts, all_opts=None): if all_opts is None: all_opts = list(range(1, self.size+1)) for i in range(self.size): for opt in opts: try: possible.remove(self.board[i][x]) except ValueError: all_opts.remove(opt) except: continue return possible return all_opts def possible_square(self, y, x): def valid_opts(self, y, x): possible = self.random_list(self.size) all_valids = [] sr = int(math.sqrt(self.size)) sx = int(x/sr) sy = int(y/sr) row_v = self.row_valids(y) col_v = self.col_valids(x) sqr_v = self.sqr_valids(y,x) for i in range(1, self.size+1): if i in row_v and i in col_v and i in sqr_v: all_valids.append(i) for xr in range(sx,sx+sr): for yr in range(sy,sy+sr): try: possible.remove(self.board[yr][xr]) except ValueError: continue return all_valids return possible def col_valids(self, x): used = [] for y in range(self.size): value = self.board[y][x] if value > 0: used.append(value) def row_has(self, y, n): return self.invert_opts(used) if n in self.board[y]: return True def row_valids(self, y): used = [] for x in range(self.size): value = self.board[y][x] if value > 0: used.append(value) return False return self.invert_opts(used) def col_has(self, x, n): def sqr_valids(self, y, x): for i in range(self.size): if self.board[i][x] == n: return True used = [] return False root = int(math.sqrt(self.size)) sx = int(x/root) sy = int(y/root) for xr in range(sx*root,(sx+1)*root): for yr in range(sy*root,(sy+1)*root): value = self.board[yr][xr] if value > 0: used.append(value) return self.invert_opts(used) def count_zeros(self): count = 0 for y in range(self.size): for x in range(self.size): if self.board[y][x] < 1: count += 1 return count def solve_board(self): zeroes = self.count_zeros() while zeroes > 0: beg_zeroes = zeroes for y in range(self.size): for x in range(self.size): value = self.board[y][x] if value == 0: opts = self.valid_opts(y,x) #print('(%s, %s): %s' % (y,x, opts)) if len(opts) == 1: self.board[y][x] = opts zeroes -= 1 self.log.append( [(y,x), opts] ) continue #if len(opts) > 1: # redu_col = self.reduce_col(x) # if len(redu_col) == 1: # self.board[y][x] = redu_col # self.log.append( [(y,x), redu_col] ) # zeroes -= 1 # continue # redu_row = self.reduce_row(y) # if len(redu_row) == 1: # self.board[y][x] = redu_row # self.log.append( [(y,x), redu_row] ) # zeroes -= 1 # continue if len(opts) == 0: raise Exception('Unsolvable board at (%s,%s)' % (y,x)) if zeroes == beg_zeroes: raise Exception('Unsolvable board: no valid moves') return True def reduce_col(self, x): sets = [] for y in range(self.size): value = self.board[y][x] if value == 0: sets.append(self.valid_opts(y,x)) #print('RCOL: %s' % sets) return self.reduce_set(*sets) def reduce_row(self, y): sets = [] for x in range(self.size): value = self.board[y][x] if value == 0: sets.append(self.valid_opts(y,x)) #print('RROW: %s' % sets) return self.reduce_set(*sets) def reduce_sqr(self, y, x): pass def reduce_set(self, *args): all_opts = [] for arg in args: if not arg: return arg for opt in arg: if opt not in all_opts: all_opts.append(opt) rm = [] for opt in all_opts: in_all = True for arg in args: if opt not in arg: in_all = False break if in_all: rm.append(opt) return self.invert_opts(rm, all_opts) ... ...
 from sudoku import Sudoku s = Sudoku(size=9) s.random_board() s.load_board_file('hard.txt') print(s) try: s.solve_board() except Exception as e: print(s.log) raise e print(s) ... ...