dancing_links/
queens.rs

1//! The [`n` queens puzzle](https://en.wikipedia.org/wiki/Eight_queens_puzzle)
2//!  is the problem of placing `n` chess queens on an `n`×`n` chessboard so that
3//! no two queens threaten each other.
4//!
5//! A solution to the problem requires that no two queens share the same row,
6//! column, or diagonal.
7
8use crate::ExactCover;
9use std::collections::HashSet;
10
11/// An instance of the `n` queens problem.
12#[derive(Debug)]
13pub struct NQueens {
14    /// The list of possible positions that could solve the `n` queens puzzle.
15    pub possibilities: Vec<Possibility>,
16    /// The list of constraints that must be satisfied for this `n` queens
17    /// puzzle.
18    pub constraints: Vec<Constraint>,
19    /// The length of the chess board side, equal to `n`.
20    pub side_length: usize,
21}
22
23impl NQueens {
24    /// Create a new instance of the `n` queens problem with the given filled
25    /// values and side length.
26    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/// A position on the chess board.
88#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
89pub struct Possibility {
90    /// The row index, ranging from 0 to `n - 1`.
91    pub row: usize,
92    /// The column index, ranging form 0 to `n - 1`.
93    pub column: usize,
94}
95
96impl Possibility {
97    /// Return an iterator over all positions on the chess board for a given
98    /// side length.
99    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    /// Return the leading diagonal index for a given side length.
105    ///
106    /// This value ranges from 0 to `n - 2`.
107    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    /// Return the trailing diagonal index.
112    ///
113    /// The value ranges from 0 to `n - 2`.
114    pub fn trailing_diagonal(self) -> usize {
115        self.row + self.column
116    }
117
118    /// Return an iterator over all the `Constraint`s that are satisfied by this
119    /// `Possibility`.
120    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/// A condition which must be satisfied in order to solve an `n` queens puzzle.
136#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
137pub enum Constraint {
138    /// A condition that a given row should have exactly one queen.
139    Row {
140        /// The row index
141        index: usize,
142    },
143    /// A condition that a given column should have exactly one queen.
144    Column {
145        /// The column index
146        index: usize,
147    },
148    /// A condition that a leading diagonal should have at most one queen.
149    LeadingDiagonal {
150        /// The leading diagonal index
151        index: usize,
152    },
153    /// A condition that a trailing diagonal should have at most one queen.
154    TrailingDiagonal {
155        /// The trailing diagonal index
156        index: usize,
157    },
158}
159
160impl Constraint {
161    /// Return an iterator over all possible `Constraint`s for a given
162    /// `side_length`.
163    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)] // takes too long on miri
247    fn count_medium_board() {
248        let queens = NQueens::new(8, iter::empty());
249
250        assert_eq!(queens.solver().count(), 92);
251    }
252}