1use crate::ExactCover;
6use std::collections::HashSet;
7
8#[derive(Debug)]
10pub struct LatinSquare {
11 pub possibilities: Vec<Possibility>,
14 pub constraints: Vec<Constraint>,
17}
18
19impl LatinSquare {
20 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#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
84pub struct Possibility {
85 pub row: usize,
89
90 pub column: usize,
94
95 pub value: usize,
99}
100
101impl Possibility {
102 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 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 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#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
143pub enum Constraint {
144 RowNumber {
147 row: usize,
149 value: usize,
151 },
152 ColumnNumber {
155 column: usize,
157 value: usize,
159 },
160 RowColumn {
162 row: usize,
164 column: usize,
166 },
167}
168
169impl Constraint {
170 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}