dancing_links/
latin_square.rs

1//! A [Latin square](https://en.wikipedia.org/wiki/Latin_square) is a
2//!  n × n array filled with n different symbols, each occurring exactly once in
3//! each row and exactly once in each column.
4
5use crate::ExactCover;
6use std::collections::HashSet;
7
8/// Instance of a Latin square puzzle.
9#[derive(Debug)]
10pub struct LatinSquare {
11    /// The list of possible positions + values that could solve the Latin
12    /// square puzzle.
13    pub possibilities: Vec<Possibility>,
14    /// The list of constraints that must be satisfied for this Latin square
15    /// puzzle.
16    pub constraints: Vec<Constraint>,
17}
18
19impl LatinSquare {
20    /// Create a new Latin square puzzle.
21    ///
22    /// The puzzle has dimensions `side_length` × `side_length` and the given
23    /// list of filled values.
24    pub fn new(side_length: usize, filled_values: impl IntoIterator<Item = Possibility>) -> Self {
25        let filled_values: Vec<_> = filled_values
26            .into_iter()
27            .inspect(|poss| {
28                debug_assert!(
29                    0 < poss.value && poss.value <= side_length,
30                    "Symbol values should be in range (1..=side_length)"
31                )
32            })
33            .collect();
34
35        let satisfied: HashSet<_> = filled_values
36            .iter()
37            .copied()
38            .flat_map(Possibility::satisfied_constraints)
39            .collect();
40
41        let filled_coordinates: HashSet<_> = filled_values
42            .iter()
43            .map(|poss| (poss.row, poss.column))
44            .collect();
45
46        let possibilities: Vec<_> = Possibility::all(side_length)
47            .filter(|poss| !filled_coordinates.contains(&(poss.row, poss.column)))
48            .collect();
49
50        let constraints = Constraint::all(side_length)
51            .filter(|cons| !satisfied.contains(cons))
52            .collect();
53
54        Self {
55            possibilities,
56            constraints,
57        }
58    }
59}
60
61impl ExactCover for LatinSquare {
62    type Constraint = Constraint;
63    type Possibility = Possibility;
64
65    fn satisfies(&self, poss: &Self::Possibility, cons: &Self::Constraint) -> bool {
66        poss.satisfies(cons)
67    }
68
69    fn is_optional(&self, _cons: &Self::Constraint) -> bool {
70        false
71    }
72
73    fn possibilities(&self) -> &[Self::Possibility] {
74        &self.possibilities
75    }
76
77    fn constraints(&self) -> &[Self::Constraint] {
78        &self.constraints
79    }
80}
81
82/// A position and value for a box inside of a Latin square puzzle.
83#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
84pub struct Possibility {
85    /// The row position of the box.
86    ///
87    /// The values ranges from 0 to `side_length - 1`.
88    pub row: usize,
89
90    /// The column position of the box.
91    ///
92    /// The values ranges from 0 to `side_length - 1`.
93    pub column: usize,
94
95    /// The value present inside of the box.
96    ///
97    /// The values ranges from 1 to `side_length`.
98    pub value: usize,
99}
100
101impl Possibility {
102    /// Return an iterator over all possible `Possibility`s for the given
103    /// `side_length`.
104    pub fn all(side_length: usize) -> impl Iterator<Item = Self> {
105        crate::util::three_combination_iter([side_length, side_length, side_length + 1], [0, 0, 1])
106            .map(|[column, row, value]| Possibility { row, column, value })
107    }
108
109    /// Return an iterator over all `Constraint`s that are satisfied by this
110    /// `Possibility`.
111    pub fn satisfied_constraints(self) -> impl Iterator<Item = Constraint> {
112        [
113            Constraint::RowNumber {
114                row: self.row,
115                value: self.value,
116            },
117            Constraint::ColumnNumber {
118                column: self.column,
119                value: self.value,
120            },
121            Constraint::RowColumn {
122                row: self.row,
123                column: self.column,
124            },
125        ]
126        .into_iter()
127    }
128
129    /// Return true if this `Possibility` satisfies the given `Constraint`.
130    pub fn satisfies(&self, constraint: &Constraint) -> bool {
131        use Constraint::*;
132
133        match constraint {
134            RowNumber { row, value } => self.row == *row && self.value == *value,
135            ColumnNumber { column, value } => self.column == *column && self.value == *value,
136            RowColumn { row, column } => self.row == *row && self.column == *column,
137        }
138    }
139}
140
141/// A condition which must be satisfied in order to solve a Latin square puzzle.
142#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
143pub enum Constraint {
144    /// A condition that each row should only have a single instance of a
145    /// numeric value.
146    RowNumber {
147        /// The row index
148        row: usize,
149        /// The unique numeric value
150        value: usize,
151    },
152    /// A condition that each column should only have a single instance of a
153    /// numeric value.
154    ColumnNumber {
155        /// The column index
156        column: usize,
157        /// The unique numeric value
158        value: usize,
159    },
160    /// A condition that each row, column pair should exist exactly once.
161    RowColumn {
162        /// The row index
163        row: usize,
164        /// The column index
165        column: usize,
166    },
167}
168
169impl Constraint {
170    /// Return an iterator over all possibly `Constraint`s for the given
171    /// `side_length`.
172    pub fn all(side_length: usize) -> impl Iterator<Item = Constraint> {
173        let row_number_it =
174            crate::util::two_combination_iter([side_length, side_length + 1], [0, 1])
175                .map(|[row, value]| Constraint::RowNumber { row, value });
176
177        let column_number_it =
178            crate::util::two_combination_iter([side_length, side_length + 1], [0, 1])
179                .map(|[column, value]| Constraint::ColumnNumber { column, value });
180
181        let row_column_it = crate::util::two_combination_iter([side_length, side_length], [0, 0])
182            .map(|[row, column]| Constraint::RowColumn { row, column });
183
184        row_number_it.chain(column_number_it).chain(row_column_it)
185    }
186}
187
188#[cfg(test)]
189pub(crate) mod tests {
190    use super::*;
191
192    pub(crate) fn p(row: usize, column: usize, value: usize) -> Possibility {
193        Possibility { row, column, value }
194    }
195
196    fn c_row(row: usize, value: usize) -> Constraint {
197        Constraint::RowNumber { row, value }
198    }
199
200    fn c_col(column: usize, value: usize) -> Constraint {
201        Constraint::ColumnNumber { column, value }
202    }
203
204    fn c_row_col(row: usize, column: usize) -> Constraint {
205        Constraint::RowColumn { row, column }
206    }
207
208    #[test]
209    fn check_all_possibilities() {
210        let some_possibilities: Vec<_> = Possibility::all(2).collect();
211
212        assert_eq!(
213            &some_possibilities,
214            &[
215                p(0, 0, 1),
216                p(0, 0, 2),
217                p(1, 0, 1),
218                p(1, 0, 2),
219                p(0, 1, 1),
220                p(0, 1, 2),
221                p(1, 1, 1),
222                p(1, 1, 2),
223            ]
224        );
225    }
226
227    #[test]
228    fn check_generated_possibilities_constraints() {
229        let mut square = LatinSquare::new(2, vec![p(0, 0, 1), p(0, 1, 2)]);
230
231        square.possibilities.sort();
232        assert_eq!(
233            square.possibilities,
234            vec![p(1, 0, 1), p(1, 0, 2), p(1, 1, 1), p(1, 1, 2)]
235        );
236        square.constraints.sort();
237        assert_eq!(
238            square.constraints,
239            vec![
240                c_row(1, 1),
241                c_row(1, 2),
242                c_col(0, 2),
243                c_col(1, 1),
244                c_row_col(1, 0),
245                c_row_col(1, 1)
246            ]
247        );
248    }
249
250    #[test]
251    fn solve_small_latin_square() {
252        let square = LatinSquare::new(2, vec![p(0, 0, 1), p(0, 1, 2)]);
253        let mut solver = square.solver();
254        let solutions = solver.all_solutions();
255
256        assert_eq!(solutions.len(), 1);
257        assert_eq!(solutions[0], vec![&p(1, 0, 2), &p(1, 1, 1)]);
258    }
259
260    #[test]
261    fn solve_multi_solution_latin_square() {
262        let square = LatinSquare::new(2, vec![]);
263        let mut solver = square.solver();
264        let solutions = solver.all_solutions();
265
266        assert_eq!(solutions.len(), 2);
267
268        assert_eq!(
269            solutions[0],
270            vec![&p(0, 0, 1), &p(0, 1, 2), &p(1, 1, 1), &p(1, 0, 2)]
271        );
272        assert_eq!(
273            solutions[1],
274            vec![&p(0, 1, 1), &p(0, 0, 2), &p(1, 0, 1), &p(1, 1, 2)]
275        );
276    }
277
278    #[test]
279    fn solve_impossible_latin_square() {
280        let square = LatinSquare::new(2, vec![p(0, 0, 1), p(0, 1, 1)]);
281        let mut solver = square.solver();
282        let solutions = solver.all_solutions();
283
284        assert_eq!(solutions.len(), 0);
285    }
286}