1use crate::ExactCover;
9use std::collections::HashSet;
10
11#[derive(Debug)]
13pub struct NQueens {
14 pub possibilities: Vec<Possibility>,
16 pub constraints: Vec<Constraint>,
19 pub side_length: usize,
21}
22
23impl NQueens {
24 pub fn new(side_length: usize, filled_values: impl IntoIterator<Item = Possibility>) -> Self {
27 let filled_values: Vec<_> = filled_values.into_iter().collect();
28
29 let satisfied: HashSet<_> = filled_values
30 .iter()
31 .copied()
32 .flat_map(|poss| poss.satisfied_constraints(side_length))
33 .collect();
34
35 let filled_coordinates: HashSet<_> = filled_values
36 .iter()
37 .map(|poss| (poss.row, poss.column))
38 .collect();
39
40 let possibilities: Vec<_> = Possibility::all(side_length)
41 .filter(|poss| !filled_coordinates.contains(&(poss.row, poss.column)))
42 .collect();
43
44 let constraints = Constraint::all(side_length)
45 .filter(|cons| !satisfied.contains(cons))
46 .collect();
47
48 Self {
49 possibilities,
50 constraints,
51 side_length,
52 }
53 }
54}
55
56impl ExactCover for NQueens {
57 type Constraint = Constraint;
58 type Possibility = Possibility;
59
60 fn satisfies(&self, poss: &Self::Possibility, cons: &Self::Constraint) -> bool {
61 use Constraint::*;
62
63 match cons {
64 Row { index } => poss.row == *index,
65 Column { index } => poss.column == *index,
66 LeadingDiagonal { index } => poss.leading_diagonal(self.side_length) == *index,
67 TrailingDiagonal { index } => poss.trailing_diagonal() == *index,
68 }
69 }
70
71 fn is_optional(&self, cons: &Self::Constraint) -> bool {
72 matches!(
73 cons,
74 Constraint::LeadingDiagonal { .. } | Constraint::TrailingDiagonal { .. }
75 )
76 }
77
78 fn possibilities(&self) -> &[Self::Possibility] {
79 &self.possibilities
80 }
81
82 fn constraints(&self) -> &[Self::Constraint] {
83 &self.constraints
84 }
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
89pub struct Possibility {
90 pub row: usize,
92 pub column: usize,
94}
95
96impl Possibility {
97 pub fn all(side_length: usize) -> impl Iterator<Item = Self> {
100 crate::util::two_combination_iter([side_length, side_length], [0, 0])
101 .map(|[column, row]| Possibility { row, column })
102 }
103
104 pub fn leading_diagonal(self, side_length: usize) -> usize {
108 ((self.column as i128 - self.row as i128) + (side_length - 1) as i128) as usize
109 }
110
111 pub fn trailing_diagonal(self) -> usize {
115 self.row + self.column
116 }
117
118 pub fn satisfied_constraints(self, side_length: usize) -> impl Iterator<Item = Constraint> {
121 [
122 Constraint::Row { index: self.row },
123 Constraint::Column { index: self.column },
124 Constraint::LeadingDiagonal {
125 index: self.leading_diagonal(side_length),
126 },
127 Constraint::TrailingDiagonal {
128 index: self.trailing_diagonal(),
129 },
130 ]
131 .into_iter()
132 }
133}
134
135#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
137pub enum Constraint {
138 Row {
140 index: usize,
142 },
143 Column {
145 index: usize,
147 },
148 LeadingDiagonal {
150 index: usize,
152 },
153 TrailingDiagonal {
155 index: usize,
157 },
158}
159
160impl Constraint {
161 pub fn all(side_length: usize) -> impl Iterator<Item = Constraint> {
164 let row_it = (0..side_length).map(|index| Constraint::Row { index });
165 let column_it = (0..side_length).map(|index| Constraint::Column { index });
166 let leading_it =
167 (0..(2 * side_length - 1)).map(|index| Constraint::LeadingDiagonal { index });
168 let trailing_it =
169 (0..(2 * side_length - 1)).map(|index| Constraint::TrailingDiagonal { index });
170
171 row_it.chain(column_it).chain(leading_it).chain(trailing_it)
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use std::iter;
179
180 fn p(row: usize, column: usize) -> Possibility {
181 Possibility { row, column }
182 }
183
184 #[test]
185 fn check_diagonal_indices() {
186 let side_length = 8;
187 let leading_possibilities_it = (0..side_length)
188 .rev()
189 .map(|row| Possibility { row, column: 0 })
190 .chain((1..side_length).map(|column| Possibility { row: 0, column }));
191
192 let leading_diagonal_indices: Vec<_> = leading_possibilities_it
193 .map(|poss| poss.leading_diagonal(side_length))
194 .collect();
195
196 assert_eq!(leading_diagonal_indices, (0..15).collect::<Vec<_>>());
197
198 let trailing_possibilities_it = (0..side_length)
199 .map(|column| Possibility { column, row: 0 })
200 .chain((1..side_length).map(|row| Possibility {
201 row,
202 column: side_length - 1,
203 }));
204
205 let trailing_diagonal_indices: Vec<_> = trailing_possibilities_it
206 .map(|poss| poss.trailing_diagonal())
207 .collect();
208 assert_eq!(trailing_diagonal_indices, (0..15).collect::<Vec<_>>());
209 }
210
211 #[test]
212 fn check_tiny_boards() {
213 let size_one_board = NQueens::new(1, iter::empty());
214 let size_one_solutions = size_one_board.solver().all_solutions();
215
216 assert_eq!(size_one_solutions.len(), 1);
217 assert_eq!(size_one_solutions[0], vec![&p(0, 0)]);
218
219 let size_two_board = NQueens::new(2, iter::empty());
220 assert_eq!(size_two_board.solver().count(), 0);
221
222 let size_three_board = NQueens::new(3, iter::empty());
223 assert_eq!(size_three_board.solver().count(), 0);
224 }
225
226 #[test]
227 fn check_small_board() {
228 let queens = NQueens::new(4, iter::empty());
229 let mut solver = queens.solver();
230
231 let mut first_solution = solver.next().unwrap();
232 first_solution.sort();
233 assert_eq!(first_solution, vec![&p(0, 1), &p(1, 3), &p(2, 0), &p(3, 2)]);
234
235 let mut second_solution = solver.next().unwrap();
236 second_solution.sort();
237 assert_eq!(
238 second_solution,
239 vec![&p(0, 2), &p(1, 0), &p(2, 3), &p(3, 1)]
240 );
241
242 assert!(solver.next().is_none());
243 }
244
245 #[test]
246 #[cfg_attr(miri, ignore)] fn count_medium_board() {
248 let queens = NQueens::new(8, iter::empty());
249
250 assert_eq!(queens.solver().count(), 92);
251 }
252}