exact_covers/
lib.rs

1// The following doc comment is kept in sync with the README.md file. Please
2// run the `cargo sync-readme` command after modifying the comment contents.
3//! This crate provides implementations of D. E. Knuth and C. Solnon's
4//! algorithms for solving the exact cover problem with color controls.
5//!
6//! Suppose we're given a collection $\mathcal{O}$ of _options_, each of which is
7//! a set of _items_; the _exact cover_ problem is to find a subcollection
8//! $\mathcal{O}^\star\subseteq\mathcal{O}$ of options such that each item occurs
9//! in exactly one of $\mathcal{O}^\star$'s options. Knuth proposed a method that
10//! achieves this goal in the paper "Dancing Links", [arXiv:cs/0011047][dl] [cs.DS]
11//! (2000), whose title refers to a clever yet simple technique for deleting and
12//! restoring the nodes of a doubly linked list. His backtracking scheme, called
13//! _Algorithm X_, employs this "waltzing" of links to visit all exact covers
14//! with options $\mathcal{O}$ in a recursive, depth-first manner. [For further
15//! information, see Section 7.2.2.1 of [_The Art of Computer Programming_ **4B** (2022)][taocp4b],
16//! Part 2, 65–70.]
17//!
18//! A slight modification of Algorithm X solves the considerably more general
19//! problem in which items fall into one of two categories: _primary_ and _secondary_.
20//! Now the task is to find a subcollection $\mathcal{O}^\star\subseteq\mathcal{O}$
21//! of options that cover every primary item _exactly_ once, while covering every
22//! secondary item _at most_ once. The _exact covering with colors_ (XCC) problem
23//! arises if we go further and assign a _color_ to the secondary items of each
24//! option. Then we say two options are _compatible_ if their secondary items
25//! have matching colors, and we define a solution as a collection $\mathcal{O}^\star\subseteq\mathcal{O}$
26//! of mutually compatible options that cover every primary item exactly once.
27//! (In contrast to the uncolored case, a secondary item can occur in more than
28//! one of $\mathcal{O}^\star$'s options provided that their colors are compatible.)
29//!
30//! Fortunately the dancing links technique is also well suited to XCC problems.
31//! Knuth proved this when he introduced Algorithm C in Section 7.2.2.1 of
32//! [_TAOCP_ **4B**][taocp4b], part 2, pages 87–91; his new procedure extends
33//! Algorithm X to colors. In 2020, C. Solnon suggested using the sparse-set
34//! data structure of P. Briggs and L. Torczon [[_ACM Letters on Programming Languages and Systems_ **2** (1993)][sparsesets],
35//! 59–69] to store the database of currently active options and the list of
36//! items that need to be covered. Knuth prepared an implementation of this
37//! approach, called the "dancing cells" method, using the conventions of
38//! Algorithm C. Numerous benchmark tests for these two XCC solvers appear
39//! in Section 7.2.2.3 of [_TAOCP_ **4C** (June 25, 2024)][taocp4c], Pre-Fascicle
40//! 7A (preliminary draft), pages 64–65. To summarize the results of these
41//! experiments: there is no clear winner, and we don't yet know a rule for
42//! determining which method is best for a particular instance of XCC.
43//!
44//! This crate is a library of subroutines for color-controlled covering of
45//! $N_1\ge0$ primary items and $N_2\ge0$ secondary items in the Rust
46//! programming language. The following structures are its most important pieces:
47//! - [`DlSolver`] finds all solutions to an XCC problem. It implements
48//!   a slightly modified form of Knuth's Algorithm 7.2.2.1C.
49//! - [`DcSolver`] adheres to the same input and output conventions as the
50//!   previous structure, but it uses the dancing cells technique.
51//!
52//! Both implementations support option simplification via the removal of
53//! _blocking_ and _forcing_. [For a discussion of these preprocessing operations,
54//! see [Knuth, _TAOCP_ **4B** (2022)][taocp4b], Part 2, 108–111.]
55//!
56//! Also, the [`examples`] directory contains an instructive set of programs
57//! that apply these algorithms to a variety of problems:
58//! - [`langford_pairs.rs`] finds all [Langford pairings] of $2n$ numbers.
59//! - [`polycube_packing.rs`] computes the number of ways to arrange 25 [Y pentacubes]
60//!   in a $5\times5\times5$ cuboid.
61//! - [`domino_chessboard.rs`] finds all ways to pack 32 dominoes into a chessboard.
62//!
63//! [dl]: https://arxiv.org/pdf/cs/0011047.pdf
64//! [taocp4b]: https://www-cs-faculty.stanford.edu/~knuth/taocp.html#vol4
65//! [taocp4c]: https://cs.stanford.edu/~knuth/fasc7a.ps.gz
66//! [sparsesets]: https://dl.acm.org/doi/pdf/10.1145/176454.176484
67//! [`examples`]: https://github.com/hsanzg/exact-covers/tree/main/examples
68//! [`langford_pairs.rs`]: https://github.com/hsanzg/exact-covers/blob/main/examples/langford_pairs.rs
69//! [Langford pairings]: https://en.wikipedia.org/wiki/Langford_pairing
70//! [`polycube_packing.rs`]: https://github.com/hsanzg/exact-covers/blob/main/examples/polycube_packing.rs
71//! [Y pentacubes]: https://en.wikipedia.org/wiki/Polycube
72//! [`domino_chessboard.rs`]: https://github.com/hsanzg/exact-covers/blob/main/examples/domino_chessboard.rs
73
74mod dc;
75mod dl;
76mod indices;
77
78pub use dc::Solver as DcSolver;
79pub use dl::Solver as DlSolver;
80use std::marker::PhantomData;
81use std::ops::ControlFlow;
82
83/// Visits all [solutions] to an XCC problem with $N_1\ge0$ primary items and
84/// $N_2\ge0$ secondary items.
85///
86/// See the [crate-level documentation](`crate`) for details.
87///
88/// # Notes
89///
90/// The solver does not visit any solution containing a purely secondary option
91/// (that is, an option that uses no primary items). However, the set of items
92/// covered by the options in any visited solution intersects every purely
93/// secondary option. This strategy is a generalized version of the "second
94/// death" method from exercise 7.2.2.1–19 of TAOCP to the XCC problem.
95///
96/// This trait is sealed, meaning that it cannot be implemented outside of the
97/// `exact-covers` crate.
98///
99/// # Examples
100///
101/// Suppose we want to cover the primary items $a,b,c,d,e,f,g$ using some of
102/// the following options:
103/// \\[
104/// \`c\\;e';\quad\`a\\;d\\;g';\quad\`b\\;c\\;f';\quad\`a\\;d\\;f';\quad\`b\\;g';\quad\`d\\;e\\;g'.
105/// \\]
106/// (D. E. Knuth posed this toy problem at the beginning of Section 7.2.2.1
107/// in [_The Art of Computer Programming_ **4B** (2022)][taocp4b], Part 2, page 66.)
108/// The following program uses an XC solver based on the dancing links method
109/// to find the unique solution $\`a\\;d\\;f';\\;\`b\\;g';\\;\`c\\;e'$:
110///
111/// ```
112/// use std::ops::ControlFlow;
113/// use exact_covers::{DlSolver, Solver};
114///
115/// let items = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
116/// let mut solver: DlSolver<char, ()> = DlSolver::new(&items, &[]);
117/// solver.add_option([          'c',      'e'         ], []);
118/// solver.add_option(['a',           'd',          'g'], []);
119/// solver.add_option([     'b', 'c',           'f'    ], []);
120/// solver.add_option(['a',           'd',      'f'    ], []);
121/// solver.add_option([     'b',                    'g'], []);
122/// solver.add_option([               'd', 'e',     'g'], []);
123///
124/// // We use an auxiliary table to store the items of an option. The chief
125/// // purpose of this reserved storage is to reduce heap allocations when
126/// // constructing the solutions to an XCC problem.
127/// let mut option = Vec::new();
128/// solver.solve(|mut solution| {
129///     assert_eq!(solution.option_count(), 3);
130///
131///     // An option is represented as a list of `(item, color)` pairs.
132///     // In this example we are not doing color-controlled covering,
133///     // so all color assignments are `None`.
134///     assert!(solution.next(&mut option));
135///     assert_eq!(option, [(&'a', None), (&'d', None), (&'f', None)]);
136///     assert!(solution.next(&mut option));
137///     assert_eq!(option, [(&'b', None), (&'g', None)]);
138///     assert!(solution.next(&mut option));
139///     assert_eq!(option, [(&'c', None), (&'e', None)]);
140///     ControlFlow::Continue(())
141/// });
142/// ```
143///
144/// Here's a slightly more interesting challenge: Given primary items $a,b,c$
145/// and secondary items $p,q$, find all subsets of options
146/// \\[
147/// \`a\\;c\\;p\\;q{:}0';\quad\`b\\;c\\;q{:}2';\quad\`a\\;p';\quad\`b\\;p{:}1';\quad\`q{:}2'
148/// \\]
149/// that (i) cover every primary item exactly once, and (ii) assign at most one
150/// color to every secondary item. (The notation $i{:}x$ means that color $x$ is
151/// assigned to item $i$; when a secondary item is not followed by a colon, it
152/// is implicitly assigned a unique color which is incompatible with any other.)
153/// The program below uses an XCC solver to obtain the solution $\`a\\;p';\\;\`b\\;c\\;q{:}2'$.
154/// Observe that the solver discards the only other solution $\`a\\;p';\\;\`b\\;c\\;q{:}2';\\;\`q{:}2'$,
155/// because it contains a purely secondary option.
156///
157/// ```
158/// use std::ops::ControlFlow;
159/// use exact_covers::{DlSolver, Solver};
160///
161/// let primary = ['a', 'b', 'c'];
162/// let secondary = ['p', 'q'];
163/// let mut solver: DlSolver<char, u8> = DlSolver::new(&primary, &secondary);
164/// solver.add_option(['a',      'c'], [('p', None),    ('q', Some(0))]);
165/// solver.add_option([     'b', 'c'], [                ('q', Some(2))]);
166/// solver.add_option(['a'          ], [('p', None)                   ]);
167/// solver.add_option([     'b',    ], [('p', Some(1))                ]);
168/// solver.add_option([             ], [                ('q', Some(2))]);
169///
170/// let mut option = Vec::new();
171/// let mut num_sols = 0;
172/// solver.solve(|mut solution| {
173///     // Notice that `color = None` if and only if `item` is primary
174///     // or was not explicitly assigned a color by `option`.
175///     assert!(solution.next(&mut option));
176///     assert_eq!(option, [(&'a', None), (&'p', None)]);
177///     assert!(solution.next(&mut option));
178///     assert_eq!(option, [(&'b', None), (&'c', None), (&'q', Some(2))]);
179///     assert!(!solution.next(&mut option));
180///     num_sols += 1;
181///     ControlFlow::Continue(())
182/// });
183/// assert_eq!(num_sols, 1);
184/// ```
185///
186/// [solutions]: `Solution`
187/// [`add_option`]: Self::add_option
188/// [taocp4b]: https://www-cs-faculty.stanford.edu/~knuth/taocp.html#vol4
189// todo: Hopefully some day we will be able to specify the `()` default type
190//       parameter for `C`, which e.g. would allow us to remove the annoying
191//       type annotation for `solver` above. As it stands, however, type inference
192//       in Rust does not use type defaults (see https://github.com/rust-lang/rust/issues/36980#issuecomment-251726254,
193//       https://github.com/rust-lang/reference/issues/636 and https://internals.rust-lang.org/t/interaction-of-user-defined-and-integral-fallbacks-with-inference/2496).
194pub trait Solver<'i, I, C>: private::Solver<'i, I, C> {
195    /// Creates a solver for an XCC problem on the given primary and secondary
196    /// items.
197    ///
198    /// To specify the options to cover these items, use [`Self::add_option`].
199    fn new(primary: &'i [I], secondary: &'i [I]) -> Self;
200
201    /// Appends an option to the XCC problem.
202    ///
203    /// The meaning of a pair $(i,c)$ in the list of secondary items depends on
204    /// the value of $c$: If $c$ is `Some(c')` for some `c'`, then the secondary
205    /// item $i$ is assigned color `c'`; otherwise $i$ is implicitly assigned
206    /// a unique color that doesn't match the color of the item in any other
207    /// option.
208    ///
209    /// Once all options have been specified, use [`Self::solve`] to visit all
210    /// solutions to the problem.
211    fn add_option<P, S>(&mut self, primary: P, secondary: S)
212    where
213        P: AsRef<[I]>,
214        S: AsRef<[(I, Option<C>)]>;
215
216    /// Calls a closure on each solution to the XCC problem.
217    ///
218    /// The solution process continues until the closure returns
219    /// [`ControlFlow::Break`] or all solutions have been visited,
220    /// whichever occurs first.
221    fn solve<F>(self, visit: F)
222    where
223        F: FnMut(Solution<'_, 'i, I, C, Self>) -> ControlFlow<()>;
224}
225
226pub(crate) mod private {
227    use crate::indices::InstIndex;
228
229    pub trait Solver<'i, I, C> {
230        /// Returns the index of the [instance] in the option selected for
231        /// covering the first item covered at the given level of backtracking;
232        /// or [`None`], if the search tree is less than `level` levels deep.
233        ///
234        /// [instance]: crate::dl::Instance
235        fn pointer(&self, level: usize) -> Option<InstIndex>;
236
237        /// Returns the current level of the search.
238        fn level(&self) -> usize;
239
240        /// Constructs the option associated with a given [instance node] $x$,
241        /// starting with the item represented by $x$ and proceeding cyclically
242        /// from left to right.
243        ///
244        /// The resulting sequence of items replaces the previous contents
245        /// of `result`.
246        ///
247        /// # Panics
248        ///
249        /// This function panics if the node index is out of bounds.
250        ///
251        /// [instance node]: crate::dl::Node::Instance
252        fn option_of(&self, ix: InstIndex, result: &mut Vec<(&'i I, Option<C>)>);
253    }
254}
255
256/// An iterator over the options of a solution to an XCC problem.
257pub struct Solution<'s, 'i: 's, I, C, S> {
258    /// The solver that found the colored covering.
259    solver: &'s mut S,
260    /// The index of an element in `solver`'s [list of node pointers], which
261    /// corresponds to an ancestor of the present solution in the search tree.
262    /// The [`Self::next`] function uses this information to reconstruct the
263    /// option selected by the search algorithm at that level of recursion.
264    ///
265    /// [list of node pointers]: private::Solver::pointer
266    level: usize,
267    _phantom: PhantomData<(&'i I, C)>,
268}
269
270impl<'s, 'i, S, I, C> Solution<'s, 'i, I, C, S>
271where
272    S: Solver<'i, I, C>,
273    I: Eq,
274{
275    /// Places the items in the next option of the solution and their
276    /// color assignments under that same option into `result`.
277    ///
278    /// Returns `false` and leaves the vector untouched if and only if
279    /// all options have already been enumerated.
280    pub fn next(&mut self, result: &mut Vec<(&'i I, Option<C>)>) -> bool {
281        if let Some(node_ix) = self.solver.pointer(self.level) {
282            // Update the recursion depth level and populate `result`.
283            self.level += 1;
284            self.solver.option_of(node_ix, result);
285            true
286        } else {
287            false
288        }
289    }
290
291    /// Returns the number of options in the solution.
292    pub fn option_count(&self) -> usize {
293        self.solver.level()
294    }
295}