dancing_links/
sudoku.rs

1//! A [Sudoku puzzle](https://en.wikipedia.org/wiki/Sudoku) is a
2//! `n^2` × `n^2` array with sub-arrays of size `n` × `n`. Each row, column, and
3//! sub-array contains the values `1` through `n` with no repeats.
4
5use super::{latin_square, ExactCover};
6use std::collections::HashSet;
7
8/// An instance of a Sudoku puzzle.
9#[derive(Debug)]
10pub struct Sudoku {
11    /// The list of possible values and positions that are valid for this Sudoku
12    /// puzzle.
13    pub possibilities: Vec<Possibility>,
14    /// The list of constraints that must be satisfied for this Sudoku puzzle.
15    pub constraints: Vec<Constraint>,
16}
17
18impl Sudoku {
19    /// Create a new new Sudoku puzzle.
20    ///
21    /// The puzzle has size `n^2` × `n^2` (where `n = box_side_length`) and the
22    /// given list of filled values.
23    pub fn new(
24        box_side_length: usize,
25        filled_values: impl IntoIterator<Item = latin_square::Possibility>,
26    ) -> Self {
27        let side_length = box_side_length * box_side_length;
28        let filled_values: Vec<_> = filled_values.into_iter().collect();
29
30        let latin = latin_square::LatinSquare::new(side_length, filled_values.iter().copied());
31
32        let satisfied: HashSet<_> = filled_values
33            .iter()
34            .copied()
35            .map(|latin_poss| Possibility::from_latin(latin_poss, box_side_length))
36            .flat_map(Possibility::satisfied_constraints)
37            .collect();
38
39        let possibilities = latin
40            .possibilities
41            .into_iter()
42            .map(|latin_poss| Possibility::from_latin(latin_poss, box_side_length))
43            .collect();
44
45        let constraints = latin
46            .constraints
47            .into_iter()
48            .map(Constraint::from)
49            .chain(Constraint::all_square_number(box_side_length))
50            .filter(|cons| !satisfied.contains(cons))
51            .collect();
52
53        Self {
54            possibilities,
55            constraints,
56        }
57    }
58}
59
60impl ExactCover for Sudoku {
61    type Constraint = Constraint;
62    type Possibility = Possibility;
63
64    fn satisfies(&self, poss: &Self::Possibility, cons: &Self::Constraint) -> bool {
65        use Constraint::*;
66
67        match cons {
68            Latin(latin_cons) => {
69                <Possibility as Into<latin_square::Possibility>>::into(*poss).satisfies(latin_cons)
70            }
71            SquareNumber { square, value } => poss.square == *square && poss.value == *value,
72        }
73    }
74
75    fn is_optional(&self, _cons: &Self::Constraint) -> bool {
76        false
77    }
78
79    fn possibilities(&self) -> &[Self::Possibility] {
80        &self.possibilities
81    }
82
83    fn constraints(&self) -> &[Self::Constraint] {
84        &self.constraints
85    }
86}
87
88/// A position and value for a box inside of a Sudoku puzzle.
89#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
90pub struct Possibility {
91    /// The row position of the box.
92    ///
93    /// The values ranges from 0 to `n - 1`, where `n` is the length of the
94    /// Sudoku board.
95    pub row: usize,
96
97    /// The column position of the box.
98    ///
99    /// The values ranges from 0 to `n - 1`, where `n` is the length of the
100    /// Sudoku board.
101    pub column: usize,
102
103    /// The index of the subgrid.
104    ///
105    /// The values ranges from 0 to `n - 1`, where `n` is the length of the
106    /// Sudoku board. This field is redundant in identifying where the box is
107    /// inside of the Sudoku board, however it is necessary to speed up checking
108    /// which `Constraint`s are satisfied by this `Possibility`.
109    pub square: usize,
110
111    /// The value present inside of the box.
112    ///
113    /// The values ranges from 1 to `n`, where `n` is the length of the
114    /// Sudoku board.
115    pub value: usize,
116}
117
118impl Possibility {
119    /// Convert a `latin_square::Possibility` to a `sudoku::Possibility`.
120    pub fn from_latin(latin: latin_square::Possibility, box_side_length: usize) -> Self {
121        let side_length = box_side_length * box_side_length;
122        let index = latin.row * side_length + latin.column;
123        let square = ((index % side_length) / box_side_length)
124            + box_side_length * (index / (side_length * box_side_length));
125
126        Possibility {
127            row: latin.row,
128            column: latin.column,
129            value: latin.value,
130            square,
131        }
132    }
133
134    /// Return an iterator over the `Constraint`s that are satisfied by this
135    /// `Possibility`.
136    pub fn satisfied_constraints(self) -> impl Iterator<Item = Constraint> {
137        [
138            Constraint::Latin(latin_square::Constraint::RowNumber {
139                row: self.row,
140                value: self.value,
141            }),
142            Constraint::Latin(latin_square::Constraint::ColumnNumber {
143                column: self.column,
144                value: self.value,
145            }),
146            Constraint::Latin(latin_square::Constraint::RowColumn {
147                row: self.row,
148                column: self.column,
149            }),
150            Constraint::SquareNumber {
151                square: self.square,
152                value: self.value,
153            },
154        ]
155        .into_iter()
156    }
157}
158
159impl From<Possibility> for latin_square::Possibility {
160    fn from(src: Possibility) -> Self {
161        latin_square::Possibility {
162            row: src.row,
163            column: src.column,
164            value: src.value,
165        }
166    }
167}
168
169/// A condition which must be satisfied in order to solve a Sudoku puzzle.
170#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
171pub enum Constraint {
172    /// A constraint which is also shared by a Latin Square puzzle.
173    Latin(latin_square::Constraint),
174    /// A condition that each square (or sub-grid) should only have a single
175    /// instance of a numeric value.
176    SquareNumber {
177        /// The square index.
178        square: usize,
179        /// The unique numeric value
180        value: usize,
181    },
182}
183
184impl Constraint {
185    fn all_square_number(box_side_length: usize) -> impl Iterator<Item = Constraint> {
186        let side_length = box_side_length * box_side_length;
187
188        crate::util::two_combination_iter([side_length, side_length + 1], [0, 1])
189            .map(|[square, value]| Constraint::SquareNumber { square, value })
190    }
191}
192
193impl From<latin_square::Constraint> for Constraint {
194    fn from(src: latin_square::Constraint) -> Self {
195        Constraint::Latin(src)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    fn p(row: usize, column: usize, square: usize, value: usize) -> Possibility {
204        Possibility {
205            row,
206            column,
207            square,
208            value,
209        }
210    }
211
212    fn c_row(row: usize, value: usize) -> Constraint {
213        Constraint::Latin(latin_square::Constraint::RowNumber { row, value })
214    }
215
216    fn c_col(column: usize, value: usize) -> Constraint {
217        Constraint::Latin(latin_square::Constraint::ColumnNumber { column, value })
218    }
219
220    fn c_row_col(row: usize, column: usize) -> Constraint {
221        Constraint::Latin(latin_square::Constraint::RowColumn { row, column })
222    }
223
224    fn c_square(square: usize, value: usize) -> Constraint {
225        Constraint::SquareNumber { square, value }
226    }
227
228    #[test]
229    fn check_generated_possibilities_constraints() {
230        let mut sudoku = Sudoku::new(
231            2,
232            vec![
233                // top row
234                latin_square::tests::p(0, 0, 1),
235                latin_square::tests::p(0, 1, 2),
236                latin_square::tests::p(0, 2, 3),
237                latin_square::tests::p(0, 3, 4),
238                // middle bits
239                latin_square::tests::p(1, 0, 3),
240                latin_square::tests::p(2, 0, 2),
241                latin_square::tests::p(1, 3, 2),
242                latin_square::tests::p(2, 3, 3),
243                // bottom row
244                latin_square::tests::p(3, 0, 4),
245                latin_square::tests::p(3, 1, 3),
246                latin_square::tests::p(3, 2, 2),
247                latin_square::tests::p(3, 3, 1),
248            ],
249        );
250
251        sudoku.possibilities.sort();
252        assert_eq!(
253            sudoku.possibilities,
254            vec![
255                p(1, 1, 0, 1),
256                p(1, 1, 0, 2),
257                p(1, 1, 0, 3),
258                p(1, 1, 0, 4),
259                p(1, 2, 1, 1),
260                p(1, 2, 1, 2),
261                p(1, 2, 1, 3),
262                p(1, 2, 1, 4),
263                p(2, 1, 2, 1),
264                p(2, 1, 2, 2),
265                p(2, 1, 2, 3),
266                p(2, 1, 2, 4),
267                p(2, 2, 3, 1),
268                p(2, 2, 3, 2),
269                p(2, 2, 3, 3),
270                p(2, 2, 3, 4),
271            ]
272        );
273        sudoku.constraints.sort();
274        assert_eq!(
275            sudoku.constraints,
276            vec![
277                c_row(1, 1),
278                c_row(1, 4),
279                c_row(2, 1),
280                c_row(2, 4),
281                c_col(1, 1),
282                c_col(1, 4),
283                c_col(2, 1),
284                c_col(2, 4),
285                c_row_col(1, 1),
286                c_row_col(1, 2),
287                c_row_col(2, 1),
288                c_row_col(2, 2),
289                c_square(0, 4),
290                c_square(1, 1),
291                c_square(2, 1),
292                c_square(3, 4),
293            ]
294        );
295    }
296
297    #[test]
298    fn solve_small_sudoku() {
299        let sudoku = Sudoku::new(
300            2,
301            vec![
302                // top row
303                latin_square::tests::p(0, 0, 1),
304                latin_square::tests::p(0, 1, 2),
305                latin_square::tests::p(0, 2, 3),
306                latin_square::tests::p(0, 3, 4),
307                // middle bits
308                latin_square::tests::p(1, 0, 3),
309                latin_square::tests::p(2, 0, 2),
310                latin_square::tests::p(1, 3, 2),
311                latin_square::tests::p(2, 3, 3),
312                // bottom row
313                latin_square::tests::p(3, 0, 4),
314                latin_square::tests::p(3, 1, 3),
315                latin_square::tests::p(3, 2, 2),
316                latin_square::tests::p(3, 3, 1),
317            ],
318        );
319
320        let mut solver = sudoku.solver();
321        let solutions = solver.all_solutions();
322
323        assert_eq!(solutions.len(), 1);
324        assert_eq!(
325            solutions[0],
326            vec![
327                &p(1, 1, 0, 4),
328                &p(1, 2, 1, 1),
329                &p(2, 1, 2, 1),
330                &p(2, 2, 3, 4)
331            ]
332        );
333    }
334}