Skip to content

Commit ae383f4

Browse files
committed
feat: added sol1 to project euler 096
1 parent a71618f commit ae383f4

File tree

3 files changed

+663
-0
lines changed

3 files changed

+663
-0
lines changed

project_euler/problem_096/__init__.py

Whitespace-only changes.

project_euler/problem_096/sol1.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""
2+
Problem Statement:
3+
Su Doku (Japanese meaning number place) is the name given to a popular puzzle
4+
concept. Its origin is unclear, but credit must be attributed to Leonhard
5+
Euler who invented a similar, and much more difficult, puzzle idea called
6+
Latin Squares. The objective of Su Doku puzzles, however, is to replace
7+
the blanks (or zeros) in a 9 by 9 grid in such that each row, column, and
8+
3 by 3 box contains each of the digits 1 to 9. Below is an example of a
9+
typical starting puzzle grid and its solution grid.
10+
11+
003020600
12+
900305001
13+
001806400
14+
008102900
15+
700000008
16+
006708200
17+
002609500
18+
800203009
19+
005010300
20+
21+
483921657
22+
967345821
23+
251876493
24+
548132976
25+
729564138
26+
136798245
27+
372689514
28+
814253769
29+
695417382
30+
31+
A well constructed Su Doku puzzle has a unique solution and can be
32+
solved by logic, although it may be necessary to employ "guess and test"
33+
methods in order to eliminate options (there is much contested opinion over this).
34+
The complexity of the search determines the difficulty of the puzzle; the
35+
example above is considered easy because it can be solved by straight
36+
forward direct deduction.
37+
38+
The 6K text file, sudoku.txt (right click and 'Save Link/Target As...'),
39+
contains fifty different Su Doku puzzles ranging in difficulty, but all
40+
with unique solutions (the first puzzle in the file is the example above).
41+
42+
By solving all fifty puzzles find the sum of the 3-digit numbers found in
43+
the top left corner of each solution grid; for example, 483 is the 3-digit
44+
number found in the top left corner of the solution grid above."""
45+
46+
import os
47+
48+
49+
def solve(
50+
unfilled: list[tuple[int, int]],
51+
row: list[int],
52+
col: list[int],
53+
box: list[int],
54+
board: list[list[str]],
55+
i: int,
56+
n: int,
57+
) -> bool:
58+
"""
59+
Recursive backtracking function to solve the sudoku
60+
"""
61+
if i == n:
62+
return True
63+
64+
# Get the row and column numbers for the current unfilled cell
65+
r, c = unfilled[i]
66+
67+
for val in range(9):
68+
# Check if value (val+1) can be placed at position (r, c)
69+
if (
70+
((row[r] & (1 << val)) == 0)
71+
and ((col[c] & (1 << val)) == 0)
72+
and ((box[r // 3 * 3 + c // 3] & (1 << val)) == 0)
73+
):
74+
# Place the value
75+
row[r] ^= 1 << val
76+
col[c] ^= 1 << val
77+
box[r // 3 * 3 + c // 3] ^= 1 << val
78+
board[r][c] = str(val + 1)
79+
80+
# Recursively solve
81+
if solve(unfilled, row, col, box, board, i + 1, n):
82+
return True
83+
84+
# Backtrack
85+
row[r] ^= 1 << val
86+
col[c] ^= 1 << val
87+
box[r // 3 * 3 + c // 3] ^= 1 << val
88+
board[r][c] = "0"
89+
90+
return False
91+
92+
93+
def solve_sudoku(board: list[list[str]]) -> int:
94+
"""
95+
Solve a single sudoku puzzle and return the first 3 digits
96+
"""
97+
unfilled = []
98+
row = [0] * 9
99+
col = [0] * 9
100+
box = [0] * 9
101+
102+
# Initialize the state and find unfilled positions
103+
for i in range(0, 9, 3):
104+
for j in range(0, 9, 3):
105+
for ii in range(3):
106+
for jj in range(3):
107+
r = i + ii
108+
c = j + jj
109+
if board[r][c] == "0":
110+
unfilled.append((r, c))
111+
else:
112+
val = int(board[r][c]) - 1
113+
row[r] |= 1 << val
114+
col[c] |= 1 << val
115+
box[i + j // 3] |= 1 << val
116+
117+
# Solve the puzzle
118+
solve(unfilled, row, col, box, board, 0, len(unfilled))
119+
120+
# Return the first 3 digits as a number
121+
return int(board[0][0]) * 100 + int(board[0][1]) * 10 + int(board[0][2])
122+
123+
124+
def solution() -> int:
125+
"""
126+
Finds the sum of the 3 digit numbers formed by the 3 digits in the
127+
top left corner of the solved sudoku puzzles as described by the problem statement.
128+
129+
>>> solution()
130+
24702
131+
"""
132+
try:
133+
script_dir = os.path.dirname(os.path.realpath(__file__))
134+
sudoku = os.path.join(script_dir, "sudoku.txt")
135+
with open(sudoku) as file:
136+
lines = file.readlines()
137+
138+
except FileNotFoundError:
139+
print("Error: Could not find sudoku.txt file")
140+
return 0
141+
142+
res = 0
143+
count = 0
144+
board = []
145+
146+
for line in lines:
147+
line = line.strip()
148+
if line.startswith("G"):
149+
continue
150+
151+
board.append(list(line))
152+
count = (count + 1) % 9
153+
154+
if count == 0:
155+
solution = solve_sudoku(board)
156+
res += solution
157+
board = []
158+
159+
return res
160+
161+
162+
if __name__ == "__main__":
163+
print(f"{solution()=}")

0 commit comments

Comments
 (0)