automate sudoku boards

This commit is contained in:
2025-09-24 15:35:52 +02:00
parent 018a56f172
commit 4451ef60e1
4 changed files with 89 additions and 82 deletions

View File

@@ -98,6 +98,7 @@ class CSP:
""" """
def backtrack(csp, assignment: dict[str, Any]) -> dict | None: def backtrack(csp, assignment: dict[str, Any]) -> dict | None:
print("i have been called")
if len(assignment) == len(csp.variables): if len(assignment) == len(csp.variables):
return assignment # base-case return assignment # base-case
var = select_unassigned_variable(csp, assignment) var = select_unassigned_variable(csp, assignment)
@@ -108,6 +109,7 @@ class CSP:
if result := backtrack(csp, assignment): if result := backtrack(csp, assignment):
return result return result
assignment.pop(var) assignment.pop(var)
print("i have failed")
return None # failure return None # failure
def consistent(csp, var, value, assignment) -> bool: def consistent(csp, var, value, assignment) -> bool:

View File

@@ -4,27 +4,18 @@ author: Erlend Ulvund Skaarberg and Fredrik Robertsen
date: 2025-09-24 date: 2025-09-24
--- ---
## 2a) Solutions ## 2a,c,d,e) Sudoku boards and backtrack statistics
Here are the solutions for the provided Sudoku puzzles: These are the results of running our CSP solver on all four sudoku boards.
It seems we get lucky on the hard board.
Additionally we calculate the time it takes for running backtracking search and
AC-3 on each sudoku board.
``` ```
Solution for sudoku_easy.txt $ for f in sudoku_*; do python3 sudoku.py $f; done
8 7 5 | 9 3 6 | 1 4 2
1 6 9 | 7 2 4 | 3 8 5
2 4 3 | 8 5 1 | 6 7 9
------+-------+------
4 5 2 | 6 9 7 | 8 3 1
9 8 6 | 4 1 3 | 2 5 7
7 3 1 | 5 8 2 | 9 6 4
------+-------+------
5 1 7 | 3 6 9 | 4 2 8
6 2 8 | 1 4 5 | 7 9 3
3 9 4 | 2 7 8 | 5 1 6
================================================== True
Solution for sudoku_medium.txt
7 8 4 | 9 3 2 | 1 5 6 7 8 4 | 9 3 2 | 1 5 6
6 1 9 | 4 8 5 | 3 2 7 6 1 9 | 4 8 5 | 3 2 7
2 3 5 | 1 7 6 | 4 8 9 2 3 5 | 1 7 6 | 4 8 9
@@ -36,10 +27,10 @@ Solution for sudoku_medium.txt
4 5 3 | 7 2 9 | 6 1 8 4 5 3 | 7 2 9 | 6 1 8
8 6 2 | 3 1 4 | 7 9 5 8 6 2 | 3 1 4 | 7 9 5
1 9 7 | 6 5 8 | 2 4 3 1 9 7 | 6 5 8 | 2 4 3
Backtrack failed 357 times out of 439 calls.
================================================== Running both AC-3 and backtracking search took 0.05711
Running only backtracking search took 0.04202
Solution for sudoku_hard.txt True
1 5 2 | 3 4 6 | 8 9 7 1 5 2 | 3 4 6 | 8 9 7
4 3 7 | 1 8 9 | 6 5 2 4 3 7 | 1 8 9 | 6 5 2
6 8 9 | 5 7 2 | 3 1 4 6 8 9 | 5 7 2 | 3 1 4
@@ -51,10 +42,25 @@ Solution for sudoku_hard.txt
7 9 8 | 2 5 3 | 4 6 1 7 9 8 | 2 5 3 | 4 6 1
3 6 5 | 9 1 4 | 2 7 8 3 6 5 | 9 1 4 | 2 7 8
2 1 4 | 7 6 8 | 5 3 9 2 1 4 | 7 6 8 | 5 3 9
Backtrack failed 147 times out of 229 calls.
================================================== Running both AC-3 and backtracking search took 0.04713
Running only backtracking search took 0.03006
Solution for sudoku_very_hard.txt True
8 7 5 | 9 3 6 | 1 4 2
1 6 9 | 7 2 4 | 3 8 5
2 4 3 | 8 5 1 | 6 7 9
------+-------+------
4 5 2 | 6 9 7 | 8 3 1
9 8 6 | 4 1 3 | 2 5 7
7 3 1 | 5 8 2 | 9 6 4
------+-------+------
5 1 7 | 3 6 9 | 4 2 8
6 2 8 | 1 4 5 | 7 9 3
3 9 4 | 2 7 8 | 5 1 6
Backtrack failed 1225 times out of 1307 calls.
Running both AC-3 and backtracking search took 0.13033
Running only backtracking search took 0.11624
True
4 3 1 | 8 6 7 | 9 2 5 4 3 1 | 8 6 7 | 9 2 5
6 5 2 | 4 9 1 | 3 8 7 6 5 2 | 4 9 1 | 3 8 7
8 9 7 | 5 3 2 | 1 6 4 8 9 7 | 5 3 2 | 1 6 4
@@ -66,6 +72,9 @@ Solution for sudoku_very_hard.txt
9 4 3 | 7 2 8 | 6 5 1 9 4 3 | 7 2 8 | 6 5 1
7 6 5 | 1 4 3 | 2 9 8 7 6 5 | 1 4 3 | 2 9 8
1 2 8 | 6 5 9 | 4 7 3 1 2 8 | 6 5 9 | 4 7 3
Backtrack failed 16016 times out of 16098 calls.
Running both AC-3 and backtracking search took 2.12073
Running only backtracking search took 2.10415
``` ```
## 2b) domains ## 2b) domains
@@ -271,50 +280,16 @@ AC-3 reduced the number of domain values from 473 to 294:
X96: {1, 2, 3, 4, 5, 6, 7, 8, 9} -> {1, 3, 4, 5, 6, 7, 8, 9} X96: {1, 2, 3, 4, 5, 6, 7, 8, 9} -> {1, 3, 4, 5, 6, 7, 8, 9}
``` ```
## 2c) number of times backtrack() was called and failed
How many times backtrack() was called and how many times it failed for each of the four Sudoku puzzles.
```
Backtracking search on sudoku_easy.txt:
Backtracking search called 439 times with 357 failures.
Backtracking search on sudoku_medium.txt:
Backtracking search called 439 times with 357 failures.
Backtracking search on sudoku_hard.txt:
Backtracking search called 439 times with 357 failures.
Backtracking search on sudoku_very_hard.txt:
Backtracking search called 439 times with 357 failures.
```
## 2d,e) runtime for backtracking search and AC-3
Time taken to run AC-3, backtracking search, and the total time for each of the four Sudoku puzzles.
```
Timing on sudoku_easy.txt:
AC-3 took 0.022991 seconds.
Backtracking search took 0.041559 seconds.
Total time: 0.064553 seconds.
Timing on sudoku_medium.txt:
AC-3 took 0.023183 seconds.
Backtracking search took 0.056572 seconds.
Total time: 0.079757 seconds.
Timing on sudoku_hard.txt:
AC-3 took 0.033311 seconds.
Backtracking search took 0.053730 seconds.
Total time: 0.087044 seconds.
Timing on sudoku_very_hard.txt:
AC-3 took 0.027815 seconds.
Backtracking search took 0.139221 seconds.
Total time: 0.167038 seconds.
```
## 2f) why does AC-3 drastically reduce the runtime for backtracking search? ## 2f) why does AC-3 drastically reduce the runtime for backtracking search?
AC-3 drastically reduces the runtime for backtracking search because it prunes the search space by eliminating inconsistent values from the domains of the variables before the backtracking search begins. By enforcing arc consistency, AC-3 ensures that many impossible assignments are removed early on, which means that the backtracking algorithm has fewer options to consider when trying to assign values to variables. This reduction in the number of potential assignments leads to fewer recursive calls and backtracks during the search process, which speeds up the backtracking search significantly. We've seen this in practice by printing the number of failures during backtracking search without AC-3. Using AC-3, we reach a few hundred failures, while without AC-3, we reach hundreds of thousands of failures within a few minutes of runtime. AC-3 drastically reduces the runtime for backtracking search because it prunes
the search space by eliminating inconsistent values from the domains of the
variables before the backtracking search begins. By enforcing arc consistency,
AC-3 ensures that many impossible assignments are removed early on, which means
that the backtracking algorithm has fewer options to consider when trying to
assign values to variables. This reduction in the number of potential
assignments leads to fewer recursive calls and backtracks during the search
process, which speeds up the backtracking search significantly. We've seen this
in practice by printing the number of failures during backtracking search
without AC-3. Using AC-3, we reach a few hundred failures, while without AC-3,
we reach hundreds of thousands of failures within a few minutes of runtime.

Binary file not shown.

View File

@@ -1,6 +1,10 @@
# Sudoku problems. # Sudoku problems.
# The CSP.ac_3() and CSP.backtrack() methods need to be implemented # The CSP.ac_3() and CSP.backtrack() methods need to be implemented
import sys
from io import StringIO
from time import time
from csp import CSP, alldiff from csp import CSP, alldiff
@@ -11,16 +15,16 @@ def print_solution(solution):
""" """
for row in range(width): for row in range(width):
for col in range(width): for col in range(width):
print(solution[f'X{row+1}{col+1}'], end=" ") print(solution[f"X{row+1}{col+1}"], end=" ")
if col == 2 or col == 5: if col == 2 or col == 5:
print('|', end=" ") print("|", end=" ")
print("") print("")
if row == 2 or row == 5: if row == 2 or row == 5:
print('------+-------+------') print("------+-------+------")
# Choose Sudoku problem # Choose Sudoku problem
grid = open('sudoku_easy.txt').read().split() grid = open(sys.argv[1] if len(sys.argv) > 1 else "sudoku_easy.txt").read().split()
width = 9 width = 9
box_width = 3 box_width = 3
@@ -28,34 +32,60 @@ box_width = 3
domains = {} domains = {}
for row in range(width): for row in range(width):
for col in range(width): for col in range(width):
if grid[row][col] == '0': if grid[row][col] == "0":
domains[f'X{row+1}{col+1}'] = set(range(1, 10)) domains[f"X{row+1}{col+1}"] = set(range(1, 10))
else: else:
domains[f'X{row+1}{col+1}'] = {int(grid[row][col])} domains[f"X{row+1}{col+1}"] = {int(grid[row][col])}
edges = [] edges = []
for row in range(width): for row in range(width):
edges += alldiff([f'X{row+1}{col+1}' for col in range(width)]) edges += alldiff([f"X{row+1}{col+1}" for col in range(width)])
for col in range(width): for col in range(width):
edges += alldiff([f'X{row+1}{col+1}' for row in range(width)]) edges += alldiff([f"X{row+1}{col+1}" for row in range(width)])
for box_row in range(box_width): for box_row in range(box_width):
for box_col in range(box_width): for box_col in range(box_width):
cells = [] cells = []
edges += alldiff( edges += alldiff(
[ [
f'X{row+1}{col+1}' for row in range(box_row * box_width, (box_row + 1) * box_width) f"X{row+1}{col+1}"
for row in range(box_row * box_width, (box_row + 1) * box_width)
for col in range(box_col * box_width, (box_col + 1) * box_width) for col in range(box_col * box_width, (box_col + 1) * box_width)
] ]
) )
csp = CSP( csp = CSP(
variables=[f'X{row+1}{col+1}' for row in range(width) for col in range(width)], variables=[f"X{row+1}{col+1}" for row in range(width) for col in range(width)],
domains=domains, domains=domains,
edges=edges, edges=edges,
) )
# funny python code to hijack and redirect stdout
start_ac_3 = time()
print(csp.ac_3()) print(csp.ac_3())
print_solution(csp.backtracking_search())
original_stdout = sys.stdout
sys.stdout = StringIO()
start_backtracking_search = time()
result = csp.backtracking_search()
end_time = time()
data = sys.stdout.getvalue().splitlines()
sys.stdout = original_stdout
print_solution(result)
number_of_failures = len([x for x in data if x == "i have failed"])
number_of_function_calls = len([x for x in data if x == "i have been called"])
print(
f"Backtrack failed {number_of_failures} times out of {number_of_function_calls} calls."
)
print(f"Running both AC-3 and backtracking search took {end_time - start_ac_3:.5f}")
print(
f"Running only backtracking search took {end_time - start_backtracking_search:.5f}"
)
# Expected output after implementing csp.ac_3() and csp.backtracking_search(): # Expected output after implementing csp.ac_3() and csp.backtracking_search():
# True # True