Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 26 additions & 119 deletions homework02/sudoku.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@


def read_sudoku(path: tp.Union[str, pathlib.Path]) -> tp.List[tp.List[str]]:
"""Прочитать Судоку из указанного файла"""
path = pathlib.Path(path)
with path.open() as f:
puzzle = f.read()
Expand All @@ -20,7 +19,6 @@ def create_grid(puzzle: str) -> tp.List[tp.List[str]]:


def display(grid: tp.List[tp.List[str]]) -> None:
"""Вывод Судоку"""
width = 2
line = "+".join(["-" * (width * 3)] * 3)
for row in range(9):
Expand All @@ -35,14 +33,6 @@ def display(grid: tp.List[tp.List[str]]) -> None:


def group(values: tp.List[T], n: int) -> tp.List[tp.List[T]]:
"""
Сгруппировать значения values в список, состоящий из списков по n элементов

>>> group([1,2,3,4], 2)
[[1, 2], [3, 4]]
>>> group([1,2,3,4,5,6,7,8,9], 3)
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
"""
ans = []
for i in range(0, len(values), n):
ans.append(values[i : i + n])
Expand All @@ -51,28 +41,10 @@ def group(values: tp.List[T], n: int) -> tp.List[tp.List[T]]:


def get_row(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]:
"""Возвращает все значения для номера строки, указанной в pos

>>> get_row([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0))
['1', '2', '.']
>>> get_row([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (1, 0))
['4', '.', '6']
>>> get_row([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (2, 0))
['.', '8', '9']
"""
return grid[pos[0]]


def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]:
"""Возвращает все значения для номера столбца, указанного в pos

>>> get_col([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']], (0, 0))
['1', '4', '7']
>>> get_col([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']], (0, 1))
['2', '.', '8']
>>> get_col([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']], (0, 2))
['3', '6', '9']
"""
ans = []
for item in grid:
ans.append(item[pos[1]])
Expand All @@ -81,16 +53,6 @@ def get_col(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str


def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[str]:
"""Возвращает все значения из квадрата, в который попадает позиция pos

>>> grid = read_sudoku('puzzle1.txt')
>>> get_block(grid, (0, 1))
['5', '3', '.', '6', '.', '.', '.', '9', '8']
>>> get_block(grid, (4, 7))
['.', '.', '3', '.', '.', '1', '.', '.', '6']
>>> get_block(grid, (8, 8))
['2', '8', '.', '.', '.', '5', '.', '7', '9']
"""
ans = []
pos_y = pos[0] // 3 * 3
pos_x = pos[1] // 3 * 3
Expand All @@ -101,16 +63,6 @@ def get_block(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.List[s


def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Tuple[int, int]:
"""Найти первую свободную позицию в пазле

>>> find_empty_positions([['1', '2', '.'], ['4', '5', '6'], ['7', '8', '9']])
(0, 2)
>>> find_empty_positions([['1', '2', '3'], ['4', '.', '6'], ['7', '8', '9']])
(1, 1)
>>> find_empty_positions([['1', '2', '3'], ['4', '5', '6'], ['.', '8', '9']])
(2, 0)
"""

for i in range(len(grid)):
for j in range(len(grid[i])):
if grid[i][j] == ".":
Expand All @@ -120,17 +72,6 @@ def find_empty_positions(grid: tp.List[tp.List[str]]) -> tp.Tuple[int, int]:


def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -> tp.Set[str]:
"""Вернуть множество возможных значения для указанной позиции

>>> grid = read_sudoku('puzzle1.txt')
>>> values = find_possible_values(grid, (0,2))
>>> values == {'1', '2', '4'}
True
>>> values = find_possible_values(grid, (4,7))
>>> values == {'2', '5', '9'}
True
"""

possible_values = set("123456789")
possible_values -= set(get_row(grid, pos))
possible_values -= set(get_col(grid, pos))
Expand All @@ -139,36 +80,21 @@ def find_possible_values(grid: tp.List[tp.List[str]], pos: tp.Tuple[int, int]) -
return possible_values


def solve(grid: tp.List[tp.List[str]]) -> tp.List[tp.List[str]]:
"""Решение пазла, заданного в grid"""
""" Как решать Судоку?
1. Найти свободную позицию
2. Найти все возможные значения, которые могут находиться на этой позиции
3. Для каждого возможного значения:
3.1. Поместить это значение на эту позицию
3.2. Продолжить решать оставшуюся часть пазла

>>> grid = read_sudoku('puzzle1.txt')
>>> solve(grid)
[['5', '3', '4', '6', '7', '8', '9', '1', '2'], ['6', '7', '2', '1', '9', '5', '3', '4', '8'], ['1', '9', '8', '3', '4', '2', '5', '6', '7'], ['8', '5', '9', '7', '6', '1', '4', '2', '3'], ['4', '2', '6', '8', '5', '3', '7', '9', '1'], ['7', '1', '3', '9', '2', '4', '8', '5', '6'], ['9', '6', '1', '5', '3', '7', '2', '8', '4'], ['2', '8', '7', '4', '1', '9', '6', '3', '5'], ['3', '4', '5', '2', '8', '6', '1', '7', '9']]
"""

def solve(grid: tp.List[tp.List[str]]):
empty_position = find_empty_positions(grid)
if empty_position == (-1, -1):
return grid
possible_values = find_possible_values(grid, empty_position)
new_grid = [i[:] for i in grid]
for i in possible_values:
new_grid[empty_position[0]][empty_position[1]] = i
new_solve = solve(new_grid)
grid[empty_position[0]][empty_position[1]] = i
new_solve = solve(grid)
if new_solve:
return new_solve
return grid
grid[empty_position[0]][empty_position[1]] = "."
return None


def check_solution(solution: tp.List[tp.List[str]]) -> bool:
"""Если решение solution верно, то вернуть True, в противном случае False"""
# TODO: Add doctests with bad puzzles
s = set("123456789")
for i in range(len(solution) // 3):
for j in range(len(solution) // 3):
Expand All @@ -193,48 +119,29 @@ def check_solution(solution: tp.List[tp.List[str]]) -> bool:


def generate_sudoku(N: int) -> tp.List[tp.List[str]]:
"""Генерация судоку заполненного на N элементов

>>> grid = generate_sudoku(40)
>>> sum(1 for row in grid for e in row if e == '.')
41
>>> solution = solve(grid)
>>> check_solution(solution)
True
>>> grid = generate_sudoku(1000)
>>> sum(1 for row in grid for e in row if e == '.')
0
>>> solution = solve(grid)
>>> check_solution(solution)
True
>>> grid = generate_sudoku(0)
>>> sum(1 for row in grid for e in row if e == '.')
81
>>> solution = solve(grid)
>>> check_solution(solution)
True
"""
size_of_sudoku = 9
new_sudoku = [["."] * size_of_sudoku] * size_of_sudoku
new_sudoku = solve(new_sudoku)

N = 81 - N
for i in range(N):
while True:
grid = [["." for i in range(9)] for j in range(9)]
grid = solve(grid)
N = N if N <= 81 else 81
for i in range(81 - N):
x = randint(0, 8)
y = randint(0, 8)
while grid[x][y] == ".":
x = randint(0, 8)
y = randint(0, 8)
if new_sudoku[x][y] != ".":
new_sudoku[x][y] = "."
break
return new_sudoku
grid[x][y] = "."
return grid


if __name__ == "__main__":
for fname in ["puzzle1.txt", "puzzle2.txt", "puzzle3.txt", "hard_puzzles.txt"]:
grid = read_sudoku(fname)
display(grid)
solution = solve(grid)
if not solution:
print(f"Puzzle {fname} can't be solved")
else:
display(solution)
grid = [
["5", "3", ".", ".", "7", ".", ".", ".", "."],
["6", ".", ".", "1", "9", "5", ".", ".", "."],
[".", "9", "8", ".", ".", ".", ".", "6", "."],
["8", ".", ".", ".", "6", ".", ".", ".", "3"],
["4", ".", ".", "8", ".", "3", ".", ".", "1"],
["7", ".", ".", ".", "2", ".", ".", ".", "6"],
[".", "6", ".", ".", ".", ".", "2", "8", "."],
[".", ".", ".", "4", "1", "9", ".", ".", "5"],
[".", ".", ".", ".", "8", ".", ".", "7", "9"],
]
print(solve(grid))