add problem 96

This commit is contained in:
filifa 2025-07-18 23:46:36 -04:00
parent 2c41f67244
commit 895489ec84
2 changed files with 854 additions and 0 deletions

354
notebooks/problem0096.ipynb Normal file
View File

@ -0,0 +1,354 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "82679c10",
"metadata": {},
"source": [
"# [Su Doku](https://projecteuler.net/problem=96)\n",
"\n",
"This problem can be solved by hand!\n",
"\n",
"... as long as you like [Sudoku](https://en.wikipedia.org/wiki/Sudoku) and have some time to kill. (Sudoku is fun, so this isn't a crazy idea - [here are some solving strategies](https://www.sudokuwiki.org/Getting_Started).)\n",
"\n",
"As with many problems before, SageMath makes this trivially easy - [it has a Sudoku solver built in](https://doc.sagemath.org/html/en/reference/games/sage/games/sudoku.html). All we really need to do is the tedious part: reading the file with all the puzzles."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e906c74c",
"metadata": {},
"outputs": [],
"source": [
"rows = []\n",
"with open(\"txt/p096_sudoku.txt\") as f:\n",
" for line in f:\n",
" if not line.startswith(\"Grid\"):\n",
" rows.append([int(n) for n in line.strip()])\n",
"\n",
"grids = [matrix(rows[i:i+9]) for i in range(0, 50*9, 9)]"
]
},
{
"cell_type": "markdown",
"id": "ffc84344",
"metadata": {},
"source": [
"Here's the first puzzle again."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "bda16e8e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"+-----+-----+-----+\n",
"| 3| 2 |6 |\n",
"|9 |3 5| 1|\n",
"| 1|8 6|4 |\n",
"+-----+-----+-----+\n",
"| 8|1 2|9 |\n",
"|7 | | 8|\n",
"| 6|7 8|2 |\n",
"+-----+-----+-----+\n",
"| 2|6 9|5 |\n",
"|8 |2 3| 9|\n",
"| 5| 1 |3 |\n",
"+-----+-----+-----+"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"puzzles = [Sudoku(grid) for grid in grids]\n",
"puzzles[0]"
]
},
{
"cell_type": "markdown",
"id": "b731aba5",
"metadata": {},
"source": [
"Here's SageMath solving it, and the 49 other puzzles:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "0d272447",
"metadata": {},
"outputs": [],
"source": [
"solutions = [next(puzzle.solve()) for puzzle in puzzles]"
]
},
{
"cell_type": "markdown",
"id": "2b336120",
"metadata": {},
"source": [
"And now we sum up the numbers in the top-left corner:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2bf4c8f0",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"24702"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"first_three_digit_num = lambda s: int(s.to_string()[0:3])\n",
"sum(first_three_digit_num(s) for s in solutions)"
]
},
{
"cell_type": "markdown",
"id": "88bbc051",
"metadata": {},
"source": [
"Done!\n",
"\n",
"But of course, we should talk about what SageMath is actually *doing*. \n",
"\n",
"Sudoku falls under the broad category of [constraint satisfaction problems](https://en.wikipedia.org/wiki/Constraint_satisfaction_problem). In general, these are hard to solve efficiently, but a standard 9x9 Sudoku grid isn't too bad.\n",
"\n",
"There are [many algorithms for solving Sudoku](https://en.wikipedia.org/wiki/Sudoku_solving_algorithms), and SageMath has two at its disposal. We'll also look at a more mathematical approach to understanding the problem.\n",
"\n",
"## Backtracking\n",
"The backtracking approach is fundamentally a [depth-first search](https://en.wikipedia.org/wiki/Depth-first_search) of the \"search tree\" of possible Sudoku grids. All you do is iterate through every open cell in the grid and try every possible number for the current cell. We make a copy of the grid for each possible guess and continue to the next open cell for each copy. If no numbers 1-9 work, an earlier guess was wrong, so we \"backtrack\" to make a different guess. [Here's a nice gif visualization](https://commons.wikimedia.org/wiki/File:Sudoku_solved_by_bactracking.gif).\n",
"\n",
"To make it easier to index the boxes of our Sudoku puzzles, we'll subdivide our matrices into 3x3 submatrices."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "986e6f17",
"metadata": {},
"outputs": [],
"source": [
"for grid in grids:\n",
" grid.subdivide([3,6], [3,6])"
]
},
{
"cell_type": "markdown",
"id": "6b99e6a6",
"metadata": {},
"source": [
"Here's a visualization. As a bonus, it looks more like a Sudoku grid this way."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "c39d2f13",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[0 0 3|0 2 0|6 0 0]\n",
"[9 0 0|3 0 5|0 0 1]\n",
"[0 0 1|8 0 6|4 0 0]\n",
"[-----+-----+-----]\n",
"[0 0 8|1 0 2|9 0 0]\n",
"[7 0 0|0 0 0|0 0 8]\n",
"[0 0 6|7 0 8|2 0 0]\n",
"[-----+-----+-----]\n",
"[0 0 2|6 0 9|5 0 0]\n",
"[8 0 0|2 0 3|0 0 9]\n",
"[0 0 5|0 1 0|3 0 0]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"grids[0]"
]
},
{
"cell_type": "markdown",
"id": "fa786290",
"metadata": {},
"source": [
"And here's where we apply depth-first search to solve. Once we have a valid grid with all the cells filled in, we're done!"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "452280a2",
"metadata": {},
"outputs": [],
"source": [
"from itertools import product\n",
"\n",
"def solve(grid):\n",
" positions = set(product(*(range(k) for k in grid.dimensions())))\n",
" \n",
" stack = [grid]\n",
" while stack != []:\n",
" g = stack.pop()\n",
" \n",
" nonzero_positions = set(g.nonzero_positions())\n",
" if len(nonzero_positions) == prod(g.dimensions()):\n",
" break\n",
" \n",
" # get the first empty cell\n",
" zero_positions = sorted(positions - nonzero_positions)\n",
" i, j = zero_positions[0]\n",
" \n",
" box = g.subdivision(i // 3, j // 3)\n",
" candidates = set(range(1, 10)) - set(g.row(i)) - set(g.column(j)) - set(box.list())\n",
" for c in candidates:\n",
" h = matrix(g)\n",
" h[i, j] = c\n",
" stack.append(h)\n",
" \n",
" return g"
]
},
{
"cell_type": "markdown",
"id": "68a82d79",
"metadata": {},
"source": [
"Here's the solution to the first grid again."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "116632d8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[4 8 3|9 2 1|6 5 7]\n",
"[9 6 7|3 4 5|8 2 1]\n",
"[2 5 1|8 7 6|4 9 3]\n",
"[-----+-----+-----]\n",
"[5 4 8|1 3 2|9 7 6]\n",
"[7 2 9|5 6 4|1 3 8]\n",
"[1 3 6|7 9 8|2 4 5]\n",
"[-----+-----+-----]\n",
"[3 7 2|6 8 9|5 1 4]\n",
"[8 1 4|2 5 3|7 6 9]\n",
"[6 9 5|4 1 7|3 8 2]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"solve(grids[0])"
]
},
{
"cell_type": "markdown",
"id": "b8130381",
"metadata": {},
"source": [
"*Note that this is a pretty slow implementation of backtracking.*\n",
"\n",
"The downside of this method is that if you make a bad guess early on, it might not be until much later that the grid is found to be invalid, so there will be a lot of backtracking before you can correct the first bad guess.\n",
"\n",
"The backtracking method is available to SageMath's built-in Sudoku solver, but it's not what it uses by default.\n",
"\n",
"## Graph theory\n",
"\n",
"Another way to approach solving Sudoku is to frame it as a graph theory problem.\n",
"\n",
"A Sudoku puzzle can be modeled as a [Sudoku graph](https://en.wikipedia.org/wiki/Sudoku_graph), which is a [20-regular graph](https://en.wikipedia.org/wiki/Regular_graph) where each vertex represents one of the puzzle's cells, and there's an edge between every pair of vertices that lie in the same row, column, or box. Just as the goal in the original puzzle is to put numbers in each cell so no number appears more than once in each row, column, or box, the goal in the graph formulation is to label each vertex so no two vertices with the same label are connected by an edge. If we just substitute number labels for colors, then this is analogous to a [graph coloring](https://en.wikipedia.org/wiki/Graph_coloring) problem. Specifically, it is a [precoloring extension](https://en.wikipedia.org/wiki/Precoloring_extension), since some cells (vertices) are initially assigned numbers (colors).\n",
"\n",
"I just mention this as a fun alternate way to think about Sudoku - this isn't how SageMath solves Sudoku puzzles.\n",
"\n",
"## Exact cover problem\n",
"Yet another way to approach solving a Sudoku puzzle is to formulate it as an [exact cover](https://en.wikipedia.org/wiki/Exact_cover) problem. Put succinctly, given a set $X$ and another set $S$ composed of subsets of $X$, the problem is to find $S^* \\subseteq S$ such that $S^*$ is a [partition](https://en.wikipedia.org/wiki/Partition_of_a_set) of $X$, if one exists.\n",
"\n",
"To formulate Sudoku as an exact cover problem, first consider how there are $9 \\times 9 = 81$ cells that can each hold any number 1 through 9. This totals to 729 options for filling in the grid's cells.\n",
"\n",
"Obviously, though, there are constraints imposed. I think the easiest way to think about the constraints is to list them. First, we have a (seemingly trivial, but nonetheless important) constraint on each cell:\n",
"1. Cell (1, 1) must have a number\n",
"2. Cell (1, 2) must have a number\n",
"3. Cell (1, 3) must have a number\n",
"\n",
"... and so on, 81 times.\n",
"\n",
"After this, there are constraints on the rows:\n",
"1. Row 1 must have a 1\n",
"2. Row 1 must have a 2\n",
"3. Row 1 must have a 3\n",
"\n",
"... and so on, for each number 1 through 9 and for each of the 9 rows. This results in another 81 constraints a solved board must satisfy.\n",
"\n",
"Similarly, there 81 more constraints imposed by each *column* needing to contain 1 through 9 exactly once, and 81 more constraints imposed by each *box* needing to contain 1 through 9 exactly once. This gives a grand total of 324 constraints that a valid Sudoku board must satisfy.\n",
"\n",
"Each of the 729 options for filling the cells will satisfy four of these constraints at a time (i.e. each time we fill in a cell, we satisfy one of each of the cell, row, column, and box constraints). To represent which constraints are satisfied, we can write each option as an array of 324 booleans, four of which are 1.\n",
"\n",
"We now have a model of the Sudoku board as a $729 \\times 324$ binary matrix - [here's a surprisingly readable rendering of it](https://www.stolaf.edu/people/hansonr/sudoku/exactcovermatrix.htm). To solve the puzzle, we want to select 81 rows of this matrix that, together, satisfy each one of the 324 constraints *exactly once* (if any of the constraints were satisfied more than once, that would imply we have the same number appearing more than once in one row/column/box, or that one cell has multiple numbers).\n",
"\n",
"To go back to our more formal definition of the exact cover problem:\n",
"* $X$ is a set of 324 constraints.\n",
"* $S$ is a set of 729 distinct subsets of $X$.\n",
"* The goal is to select a subset of $S$ that forms a partition of $X$.\n",
"\n",
"Since a Sudoku puzzle starts with some cells filled in, certain elements of $S$ will be required in our solution for a given puzzle.\n",
"\n",
"### Algorithm X and dancing links\n",
"[Knuth's Algorithm X](https://en.wikipedia.org/wiki/Knuth%27s_Algorithm_X) is a backtracking algorithm for the exact cover problem.\n",
"\n",
"Algorithm X can be made more efficient by applying a property Knuth named [dancing links](https://en.wikipedia.org/wiki/Dancing_Links). This concept boils down to the fact you can efficiently re-add a previously removed node to a [doubly linked list](https://en.wikipedia.org/wiki/Doubly_linked_list).\n",
"\n",
"By creating a special data structure ([shown here](https://commons.wikimedia.org/wiki/File:Dancing_links.svg)) that takes advantage of this property, you can create a very efficient implementation of Algorithm X. Such an implementation is deemed DLX, and *this* is the default algorithm that SageMath uses in its Sudoku solver."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,500 @@
Grid 01
003020600
900305001
001806400
008102900
700000008
006708200
002609500
800203009
005010300
Grid 02
200080300
060070084
030500209
000105408
000000000
402706000
301007040
720040060
004010003
Grid 03
000000907
000420180
000705026
100904000
050000040
000507009
920108000
034059000
507000000
Grid 04
030050040
008010500
460000012
070502080
000603000
040109030
250000098
001020600
080060020
Grid 05
020810740
700003100
090002805
009040087
400208003
160030200
302700060
005600008
076051090
Grid 06
100920000
524010000
000000070
050008102
000000000
402700090
060000000
000030945
000071006
Grid 07
043080250
600000000
000001094
900004070
000608000
010200003
820500000
000000005
034090710
Grid 08
480006902
002008001
900370060
840010200
003704100
001060049
020085007
700900600
609200018
Grid 09
000900002
050123400
030000160
908000000
070000090
000000205
091000050
007439020
400007000
Grid 10
001900003
900700160
030005007
050000009
004302600
200000070
600100030
042007006
500006800
Grid 11
000125400
008400000
420800000
030000095
060902010
510000060
000003049
000007200
001298000
Grid 12
062340750
100005600
570000040
000094800
400000006
005830000
030000091
006400007
059083260
Grid 13
300000000
005009000
200504000
020000700
160000058
704310600
000890100
000067080
000005437
Grid 14
630000000
000500008
005674000
000020000
003401020
000000345
000007004
080300902
947100080
Grid 15
000020040
008035000
000070602
031046970
200000000
000501203
049000730
000000010
800004000
Grid 16
361025900
080960010
400000057
008000471
000603000
259000800
740000005
020018060
005470329
Grid 17
050807020
600010090
702540006
070020301
504000908
103080070
900076205
060090003
080103040
Grid 18
080005000
000003457
000070809
060400903
007010500
408007020
901020000
842300000
000100080
Grid 19
003502900
000040000
106000305
900251008
070408030
800763001
308000104
000020000
005104800
Grid 20
000000000
009805100
051907420
290401065
000000000
140508093
026709580
005103600
000000000
Grid 21
020030090
000907000
900208005
004806500
607000208
003102900
800605007
000309000
030020050
Grid 22
005000006
070009020
000500107
804150000
000803000
000092805
907006000
030400010
200000600
Grid 23
040000050
001943600
009000300
600050002
103000506
800020007
005000200
002436700
030000040
Grid 24
004000000
000030002
390700080
400009001
209801307
600200008
010008053
900040000
000000800
Grid 25
360020089
000361000
000000000
803000602
400603007
607000108
000000000
000418000
970030014
Grid 26
500400060
009000800
640020000
000001008
208000501
700500000
000090084
003000600
060003002
Grid 27
007256400
400000005
010030060
000508000
008060200
000107000
030070090
200000004
006312700
Grid 28
000000000
079050180
800000007
007306800
450708096
003502700
700000005
016030420
000000000
Grid 29
030000080
009000500
007509200
700105008
020090030
900402001
004207100
002000800
070000090
Grid 30
200170603
050000100
000006079
000040700
000801000
009050000
310400000
005000060
906037002
Grid 31
000000080
800701040
040020030
374000900
000030000
005000321
010060050
050802006
080000000
Grid 32
000000085
000210009
960080100
500800016
000000000
890006007
009070052
300054000
480000000
Grid 33
608070502
050608070
002000300
500090006
040302050
800050003
005000200
010704090
409060701
Grid 34
050010040
107000602
000905000
208030501
040070020
901080406
000401000
304000709
020060010
Grid 35
053000790
009753400
100000002
090080010
000907000
080030070
500000003
007641200
061000940
Grid 36
006080300
049070250
000405000
600317004
007000800
100826009
000702000
075040190
003090600
Grid 37
005080700
700204005
320000084
060105040
008000500
070803010
450000091
600508007
003010600
Grid 38
000900800
128006400
070800060
800430007
500000009
600079008
090004010
003600284
001007000
Grid 39
000080000
270000054
095000810
009806400
020403060
006905100
017000620
460000038
000090000
Grid 40
000602000
400050001
085010620
038206710
000000000
019407350
026040530
900020007
000809000
Grid 41
000900002
050123400
030000160
908000000
070000090
000000205
091000050
007439020
400007000
Grid 42
380000000
000400785
009020300
060090000
800302009
000040070
001070500
495006000
000000092
Grid 43
000158000
002060800
030000040
027030510
000000000
046080790
050000080
004070100
000325000
Grid 44
010500200
900001000
002008030
500030007
008000500
600080004
040100700
000700006
003004050
Grid 45
080000040
000469000
400000007
005904600
070608030
008502100
900000005
000781000
060000010
Grid 46
904200007
010000000
000706500
000800090
020904060
040002000
001607000
000000030
300005702
Grid 47
000700800
006000031
040002000
024070000
010030080
000060290
000800070
860000500
002006000
Grid 48
001007090
590080001
030000080
000005800
050060020
004100000
080000030
100020079
020700400
Grid 49
000003017
015009008
060000000
100007000
009000200
000500004
000000020
500600340
340200000
Grid 50
300200000
000107000
706030500
070009080
900020004
010800050
009040301
000702000
000008006