-
Notifications
You must be signed in to change notification settings - Fork 0
/
base.py
executable file
·127 lines (99 loc) · 3.55 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
import argparse
import sys
import time
from abc import ABC, abstractmethod
class Puzzle(ABC):
def __init__(self, filename):
self.board = self._parse_puzzle(filename)
def solve(self):
print('-'*120)
self.print_board()
print
start_time = time.time()
self.solve_puzzle()
elapsed = time.time() - start_time
print('Elapsed time: {:.2f}s'.format(elapsed))
if self.is_solved():
print('Solution: ')
self.print_board()
else:
print('Could not solve')
print
@abstractmethod
def _parse_puzzle(self, filename):
""" Read in filename and parse board plus any other data, return Puzzle """
pass
@abstractmethod
def get_possible_vals(self, i, j):
""" return a list of possible values for the cell at i, j """
pass
def solve_puzzle(self):
"""
Recursively attempt to solve the puzzle cell by cell.
At each empty cell, get possible values and try each one, then continue
to the next empty cell and repeat.
If you have exhausted all possible values for a cell, return False
and try the next value at the previous empty location
"""
for i in range(9):
for j in range(9):
if self.board[i][j] == 0:
possible_vals = self.get_possible_vals(i, j)
for val in possible_vals:
self.board[i][j] = val
if self.solve_puzzle():
return True
self.board[i][j] = 0
return False
return True
def parse_board(self, data):
""" Parse 9 lines of 9 characters into a 9x9 matrix """
board = []
for d in data:
row = []
for j in range(9):
row.append(int(d[j]))
board.append(row)
return board
def print_board(self):
""" Print out board to console """
for i in range(9):
row = ""
for j in range(9):
val = self.board[i][j]
if val == 0:
row += '-, '
else:
row += f'{val}, '
print(f'[{row[:-2]}]')
def is_solved(self):
""" Ensure each row has numbers 1..9 """
col_counts = [0] * 9
row_counts = [0] * 9
for i in range(9):
for j in range(9):
cval = self.board[j][i]
if cval > 0:
col_counts[cval-1] += 1
rval = self.board[i][j]
if rval > 0:
row_counts[rval-1] += 1
return all(c == 9 for c in col_counts) and all(c == 9 for c in row_counts)
def get_row_vals(self, row):
""" Returns all non-zero values in the given row """
return [val for val in self.board[row] if val != 0]
def get_column_vals(self, col):
""" Returns all non-zero values in the given column """
return [row[col] for row in self.board if row[col] != 0]
def get_group_vals(self, row, col):
"""
Returns all non-zero values in the given 3x3 group
Should be overridden in puzzles like jigsaw/wraparound
"""
group_vals = []
g_row = row // 3 * 3
g_col = col // 3 * 3
for i in range(9):
group_vals.append(self.board[g_row + i // 3][g_col + i % 3])
return [g for g in group_vals if g != 0]