sudoku-solver/board.py

322 lines
12 KiB
Python
Raw Normal View History

2019-11-30 13:55:22 +01:00
"""This module contains the Board class.
It is used to represent different elements for the whole board.
"""
from square import Square
class Board:
"""The Board class contains different elements of the board."""
def __init__(self, string, debug=True):
self.debug = debug
2019-11-30 13:55:22 +01:00
self.lines = [""] * 9
i = 0
line = 0
while line < 9:
if string[i] == '\n':
line += 1
else:
self.lines[line] += string[i]
i += 1
self.columns = [""] * 9
for col in range(9):
for line in self.lines:
self.columns[col] += line[col]
self.grids = self.__compute_grid_values()
self.squares = []
2019-11-30 13:55:22 +01:00
self.__assert_lines_and_columns_have_same_values()
2019-12-02 08:51:47 +01:00
def __debug(self, string: str):
if self.debug:
print(string)
2019-12-02 08:51:47 +01:00
def __debug_pause(self, string: str = ""):
if self.debug:
input(string)
def __debug_print_board(self):
if self.debug:
print(self)
2019-11-30 13:55:22 +01:00
def __str__(self):
2019-11-30 15:21:33 +01:00
output = ""
for lin_nb, line in enumerate(self.lines):
if (lin_nb) % 3 == 0:
output += "+-------+-------+-------+\n"
for col_nb, square in enumerate(line):
if (col_nb) % 3 == 0:
output += '| '
if isinstance(square, set):
if len(square) == 1:
2019-12-02 08:51:47 +01:00
output += str(square)[2:-2]
2019-11-30 15:21:33 +01:00
else:
output += '.'
elif isinstance(square, Square):
if len(square.values) == 1:
output += str(square.values)[2:-2]
2019-11-30 15:21:33 +01:00
else:
output += '.'
else:
output += square
2019-11-30 15:21:33 +01:00
output += ' '
output += '|\n'
output += "+-------+-------+-------+\n"
2019-11-30 15:21:33 +01:00
return output
2019-11-30 13:55:22 +01:00
def __assert_lines_and_columns_have_same_values(self):
2019-11-30 15:21:33 +01:00
differences = []
for lin in range(len(self.lines)):
for col in range(len(self.columns)):
if isinstance(self.lines[lin][col], Square):
line_values = self.lines[lin][col].values
column_values = self.columns[col][lin].values
2019-11-30 15:21:33 +01:00
else:
line_values = self.lines[lin][col]
column_values = self.columns[col][lin]
2019-11-30 15:21:33 +01:00
if line_values != column_values:
2019-11-30 15:21:33 +01:00
differences.append('X')
2019-11-30 13:55:22 +01:00
assert len(differences) == 0
2019-12-02 08:51:47 +01:00
self.__debug("Lines and columns have the same values.")
def __compute_grid_values(self):
grids = [""] * 9
for lin, line in enumerate(self.lines):
for col in range(9):
if line[col] != '.':
if lin // 3 == 0 and col // 3 == 0:
grids[0] += line[col]
elif lin // 3 == 0 and col // 3 == 1:
grids[1] += line[col]
elif lin // 3 == 0 and col // 3 == 2:
grids[2] += line[col]
elif lin // 3 == 1 and col // 3 == 0:
grids[3] += line[col]
elif lin // 3 == 1 and col // 3 == 1:
grids[4] += line[col]
elif lin // 3 == 1 and col // 3 == 2:
grids[5] += line[col]
elif lin // 3 == 2 and col // 3 == 0:
grids[6] += line[col]
elif lin // 3 == 2 and col // 3 == 1:
grids[7] += line[col]
elif lin // 3 == 2 and col // 3 == 2:
grids[8] += line[col]
return [set(values) for values in grids]
2019-11-30 13:55:22 +01:00
def __compute_possible_values_from_lines(self):
"""Compute possible values for each number in a line.
Go through each line and add possible values to each square that hasn't
been found yet. The values are computed based on the fact that they
aren't already present in that same line.
"""
for lin, line in enumerate(self.lines):
self.lines[lin] = list(line)
2019-11-30 13:55:22 +01:00
for column in range(9):
if line[column] == '.':
self.lines[lin][column] = {
str(number) for number in range(1, 10)
2019-11-30 13:55:22 +01:00
if str(number) not in line
}
else:
self.lines[lin][column] = {line[column]}
2019-11-30 13:55:22 +01:00
def __compute_possible_values_from_columns(self):
"""Compute possible values for each number in a column.
Go through each column and add possible values to each square that
hasn't been found yet. The values are computed based on the fact that
they aren't already present in that same column.
"""
for col, column in enumerate(self.columns):
self.columns[col] = list(column)
2019-11-30 13:55:22 +01:00
for line in range(9):
if column[line] == '.':
self.columns[col][line] = {
str(number) for number in range(1, 10)
2019-11-30 13:55:22 +01:00
if str(number) not in column
}
else:
self.columns[col][line] = {column[line]}
2019-11-30 13:55:22 +01:00
def compute_possible_values(self):
"""Compute possible values for each square in the board."""
2019-12-02 08:51:47 +01:00
self.__debug(">>>Compute possible values")
self.__debug_pause()
2019-11-30 13:55:22 +01:00
self.__compute_possible_values_from_lines()
self.__compute_possible_values_from_columns()
2019-12-02 08:51:47 +01:00
self.__debug("Possible values computed")
self.__debug_print_board()
def compute_obligated_in_each_line(self):
"""Find obligated values per line for every number.
Go through each line. For each square, go through numbers 1 to 9. If
the number is in the square's values but not in the values of the rest
of the squares in the line then the square should take that number.
"""
2019-12-02 08:51:47 +01:00
self.__debug(">>>Compute obligated in each line")
self.__debug_pause()
for line_id, line in enumerate(self.lines):
self.__debug(f"Line n°{line_id}")
for square_id, square in enumerate(line):
self.__debug(f" square n°{square_id}, {square.values}")
# Create an empty set to store the other square's values.
other_values = set()
for other_square_id in range(9):
# ignore current square
if other_square_id != square_id:
# update other_values with values of other squares
other_values = other_values.union(
line[other_square_id].values
)
self.__debug(f" other values: {other_values}")
for number in range(1, 10):
if str(number) in square.values and \
str(number) not in other_values:
self.__debug(f" {number}")
self.lines[line_id][square_id].values = {str(number)}
2019-12-02 08:51:47 +01:00
self.__debug_print_board()
def compute_obligated_in_each_column(self):
"""Find obligated values per column for every number.
Go through each column. For each square, go through numbers 1 to 9. If
the number is in the square's values but not in the values of the rest
of the squares in the column then the square should take that number.
"""
2019-12-02 08:51:47 +01:00
self.__debug(">>>Compute obligated in each column")
self.__debug_pause()
for col_id, column in enumerate(self.columns):
self.__debug(f"Column n°{col_id}")
for square_id, square in enumerate(column):
self.__debug(f" square n°{square_id}, {square.values}")
# Create an empty set to store the other square's values.
other_values = set()
for other_square_id in range(9):
# ignore current square
if other_square_id != square_id:
# update other_values with values of other squares
other_values = other_values.union(
column[other_square_id].values
)
self.__debug(f" other values: {other_values}")
for number in range(1, 10):
if str(number) in square.values and \
str(number) not in other_values:
self.__debug(f" {number}")
self.columns[col_id][square_id].values = {str(number)}
2019-12-02 08:51:47 +01:00
self.__debug_print_board()
def create_squares(self):
"""Make each square of the board a Square object."""
2019-12-02 08:51:47 +01:00
self.__debug(">>>Create squares")
self.__debug_pause()
self.squares = []
if isinstance(self.lines[0][0], set):
for lin, line in enumerate(self.lines):
for col in range(len(self.columns)):
square = Square(lin, col, line[col])
self.lines[lin][col] = square
self.columns[col][lin] = square
self.squares.append(square)
self.__debug("Squares created")
self.__assert_lines_and_columns_have_same_values()
2019-12-02 08:51:47 +01:00
self.__debug_print_board()
def discard_with_grids(self):
"""Remove impossible values in squares, by discarding from grids."""
2019-12-02 08:51:47 +01:00
self.__debug(">>>Discard with grids")
self.__debug_pause()
for square_index, square in enumerate(self.squares):
self.__debug(f" square n°{square_index}: {square.values}")
self.__debug(f" values in grid: {self.grids[square.grid_id]}")
if len(square.values) > 1:
for value in self.grids[square.grid_id]:
if value in square.values:
square.values.discard(value)
self.__debug(f" square n°{square_index}: {square.values}")
2019-12-02 08:51:47 +01:00
self.__debug_print_board()
def intersect_lines_and_columns(self):
2019-11-30 13:55:22 +01:00
"""Remove impossible values from possibilities in lines and columns.
Go through each line and for each square intersect the set with the set
in the same square in self.columns.
Go through each column and for each square intersect the set with the
set in the same square in self.lines.
Note: you need to run compute_possible_values first.
"""
2019-12-02 08:51:47 +01:00
self.__debug(">>>Intersect lines and columns")
self.__debug_pause()
2019-11-30 13:55:22 +01:00
for lin, line in enumerate(self.lines):
for col, column in enumerate(self.columns):
self.lines[lin][col] = line[col].intersection(column[lin])
self.columns[col][lin] = column[lin].intersection(line[col])
2019-11-30 13:55:22 +01:00
self.__assert_lines_and_columns_have_same_values()
2019-12-02 08:51:47 +01:00
self.__debug_print_board()
def get_new_board(self) -> str:
"""Return the board in its current state as a string.
This methods let's us recreate a new board from a string so we can
reiterate throught the solving process.
"""
2019-12-02 08:51:47 +01:00
self.__debug(">>>Get new board")
self.__debug_pause()
output = ""
column = 0
for square in self.squares:
if len(square.values) == 1:
output += str(square.values)[2:-2]
else:
output += '.'
column += 1
if column == 9:
output += '\n'
column = 0
return output