[Sudoku](https://en.wikipedia.org/wiki/Sudoku) is a well-known logic puzzle with simple rules.
Given some digits, the objective is to fill a 9x9 grid with one digit per cell so that each column, each row, and each of the nine 3x3 blocks contain every digit from 1-9.
This means that there cannot be any repeated digits in a block, row, or column.
Many Sudoku solvers exist online, but we're going to write our own!
### List Comprehensions
Before we start writing the solver, let's look at an incredibly useful Python feature - the list comprehension.
You're probably familiar with this method of creating a list based on the contents of an iterable:
```python
lst=[]
foreleminiterable:
# f is an arbitrary function
lst.append(f(elem))
```
This can be replaced by a *list comprehension*:
```python
lst=[f(elem)foreleminiterable]
```
When solving logic puzzles, we will often want to work with grids.
List comprehensions are very useful concise tools for constructing groups of variables at once, or constructing many related conditions at once.
Another useful tool is the dictionary comprehension:
```python
dct={}
foreleminiterable:
dct[f1(elem)]=f2(elem)
# Equivalent:
dct={f1(elem):f2(elem)foreleminiterable}
```
We can also handle nested for loops that build a single list:
Since we are placing integers 1-9 in a 9x9 grid, we can use an `Int` to represent the value we will place in each cell.
We'll use a nested list comprehension to construct the grid.
```python
grid = [[Int(f'sudoku_{r}_{c}') for c in range(9)] for r in range(9)]
solver = Solver()
```
Note that the variable corresponding to each cell must have a unique z3 identifier, which we've ensured by using the row and column.
Then, we can only place digits from 1-9 in these cells.
```python
solver.add([
And(1 <= grid[r][c], grid[r][c]) <= 9
for r in range(9)
for c in range(9)
])
# Equivalent to
for r in range(9):
for c in range(9):
# Note that I can expand into multiple conditions since the
# And is top-level
solver.add(1 <= grid[r][c])
solver.add(grid[r][c] <= 9)
```
We also need to make sure that our assignment agrees with the given values.
```python
solver.add([
grid[r][c] == sudoku[r][c]
for r in range(9)
for c in range(9)
# 0 is falsy!
if sudoku[r][c]
])
# Equivalent to
for r in range(9):
for c in range(9):
# 0 is falsy!
if sudoku[r][c]:
solver.add(grid[r][c] == sudoku[r][c])
```
Next, each row and column and block must contain every value from 1-9.
There's two different ways that this can be expressed as a z3 expression.
We could take this condition literally, and assert that each value from 1-9 appears at least once in every row / column / block via an `And` of an `Or`s for each digit.
Alternatively, we could use z3's `Distinct` - there are 9 values and 9 cells, so if every cell takes on a distinct value, then every value must appear.
`Distinct` is much faster than the alternative expression.
If we used an `And` of `Or`s, z3 would have to rederive the "obvious" fact that no two cells in a group can have the same value; it's not an immediate logical conclusion.
The form in which we express our constraints matters.
Additionally, making some implicit constraints explicit can speed up the solver, by allowing it to apply its heuristics earlier.
While it's slightly contrived here, we'll see some more examples of this in other genres.
Now, the conditions:
```python
# Rows
solver.add([Distinct(grid[r]) for r in range(9)])
# Equivalent rows
for r in range(9):
solver.add(Distinct(grid[r]))
# Columns
solver.add([Distinct([grid[r][c] for r in range(9)]) for c in range(9)])
# Equivalent columns
for c in range(9):
# Extract the cell at the current column for each row
@@ -181,6 +181,7 @@ The values in the "dictionary", however, are z3-specific types, which we usually
To cast integers, we use `model[var].as_long()`.
To cast booleans, you can directly cast the value: `bool(model[bool_var])`.
We can also directly cast z3 values to strings: `str(model[var])`.
We can also evaluate expressions in the context of the model; when `model.evaluate(expr)` is called with an expression, known variables are plugged in.
[Here is a helpful StackOverflow post](https://stackoverflow.com/questions/12598408/z3-python-getting-python-values-from-model/12600208) that describes how to get Python values from various z3 types.