exact_covers/
dl.rs

1use crate::indices::{InstIndex, ItemIndex, NodeIndex};
2use crate::Solution;
3use std::iter;
4use std::marker::PhantomData;
5use std::mem::MaybeUninit;
6
7/// An item in an XCC problem.
8pub(crate) struct Item<'l, L> {
9    /// A unique identifier assigned to this item.
10    ///
11    /// This field roughly corresponds to the `NAME` member in Knuth's
12    /// data structure.
13    ///
14    /// # Invariant
15    ///
16    /// This variable is initialized if and only if this item does not represent
17    /// the special header node in a horizontal list of a [`Solver`].
18    label: MaybeUninit<&'l L>,
19    /// Possibly the previous item in a (horizontal) list of active items,
20    /// in cyclic order. The contents of this variable are preserved when
21    /// the item is removed from such linked list. This property makes it
22    /// possible to apply the dancing links technique on a list of active
23    /// items.
24    ///
25    /// This field corresponds to the `LLINK` pointer in Knuth's data structure.
26    ///
27    /// # Invariant
28    ///
29    /// If `left == right`, then this item represents the special header node
30    /// in an empty horizontal list of a [`Solver`].
31    left: ItemIndex,
32    /// Possibly the next item in a (horizontal) list of active items,
33    /// in cyclic order. The contents of this variable are preserved
34    /// when the item is removed from such linked list. (See `self.left`
35    /// for details.)
36    ///
37    /// This field corresponds to the `RLINK` pointer in Knuth's data structure.
38    right: ItemIndex,
39    /// The node of the first active option that contains this item, if any.
40    /// In other words, the first node in the vertical list for this item.
41    ///
42    /// This field corresponds to the `DLINK` pointer in Knuth's data structure.
43    ///
44    /// # Invariant
45    ///
46    /// `first_option` is [`None`] if and only if `last_option` is [`None`].
47    first_option: Option<InstIndex>,
48    /// The node of the last active option that contains this item, if any.
49    /// In other words, the last node in the vertical list for this item.
50    ///
51    /// This field corresponds to the `ULINK` pointer in Knuth's data structure.
52    last_option: Option<InstIndex>,
53    /// The number of elements in the vertical list for this item.
54    ///
55    /// # Invariants
56    ///
57    /// - `len == 0` if and only if `first_option` and `last_option` are [`None`].
58    /// - `len == 1` if and only if `first_option == last_option`.
59    len: usize,
60    /// Whether this item is secondary and appears in a chosen option that
61    /// specifies its color explicitly. This field is true if and only if
62    /// $\text{COLOR}(i)\ne0$ in Knuth's Algorithm C, and it helps us to
63    /// determine if a solution intersects every purely secondary option
64    /// in method [`is_valid_solution`].
65    ///
66    /// [`is_valid_solution`]: Solver::is_valid_solution
67    purified: bool,
68}
69
70impl<'l, L> Item<'l, L> {
71    /// Creates the head for an active list of items.
72    fn header(left: ItemIndex, right: ItemIndex) -> Self {
73        Self {
74            label: MaybeUninit::uninit(),
75            left,
76            right,
77            first_option: None,
78            last_option: None,
79            len: 0,
80            purified: false,
81        }
82    }
83
84    /// Creates an item that points to its predecessor and successor
85    /// in a horizontal list, and whose vertical list is empty.
86    fn new(label: &'l L, left: ItemIndex, right: ItemIndex) -> Self {
87        Self {
88            label: MaybeUninit::new(label),
89            left,
90            right,
91            first_option: None,
92            last_option: None,
93            len: 0,
94            purified: false,
95        }
96    }
97}
98
99/// The position of the special node in the `items` table of a [`Solver`]
100/// that serves as the head of the list of active _primary_ items; Knuth
101/// called this the _root_ in the paper "Dancing links", [arXiv:cs/0011047][dl]
102/// [cs.DS] (2000).
103///
104/// The list of active secondary items has its own header node, namely the last
105/// element in `items`. Its position thus depends on the number of items in
106/// the exact cover problem, so this constant has no secondary counterpart.
107///
108/// [dl]: https://arxiv.org/pdf/cs/0011047.pdf
109pub(crate) const PRIMARY_HEADER: ItemIndex = ItemIndex::new(0);
110
111/// An instance of some [item](`Item`) in an option, represented as
112/// an internal node in the toroidal data structures of [`Solver`].
113pub(crate) struct Instance<C> {
114    /// The item associated with this node.
115    ///
116    /// This field corresponds to the `TOP` pointer in Knuth's data structure.
117    item: ItemIndex,
118    /// The previous node in the vertical list for `item`, if any.
119    ///
120    /// This field corresponds to the `ULINK` pointer in Knuth's data structure,
121    /// except that it equals [`None`] instead of `item` when a node belongs
122    /// to the first option that contains `item`.
123    above: Option<InstIndex>,
124    /// The next node in the vertical list for `item`, if any.
125    ///
126    /// This field corresponds to the `DLINK` pointer in Knuth's data structure,
127    /// except that it equals [`None`] instead of `item` when a node belongs
128    /// to the last option that contains `item`.
129    below: Option<InstIndex>,
130    /// The color assigned to `item` by this option, if any. Otherwise
131    /// the solver implicitly assigns a unique color to this instance
132    /// that is incompatible with the colors of any other option,
133    /// provided that `item` is secondary.
134    ///
135    /// This field corresponds to the `COLOR` member in Knuth's data structure.
136    ///
137    /// # Invariant
138    ///
139    /// If `item` is a primary item, then this variable is [`None`].
140    color: Option<C>,
141    /// If this instance appears in the vertical list of a purified secondary
142    /// item, this field indicates whether the instance wants the color chosen
143    /// for the item or not. The purpose of this field, which is true if and
144    /// only if $\text{COLOR}(x)=-1$ in Knuth's Algorithm C, is to avoid
145    /// repeatedly purifying an item; see methods [`purify`] and [`unpurify`]
146    /// for details.
147    ///
148    /// [`purify`]: Solver::purify
149    /// [`unpurify`]: Solver::unpurify
150    wants_color: bool,
151}
152
153/// A node in the sequential table of a [`Solver`] that either is a separator
154/// between the items of two options, or it refers to one of these items.
155pub(crate) enum Node<C> {
156    /// A spacer node between options.
157    Spacer {
158        /// The first node in the preceding option, or [`None`] if this is
159        /// the spacer that comes before the first option.
160        ///
161        /// This field is an aid to traversing such option in cyclic order,
162        /// from left to right. It corresponds to the `ULINK` pointer in
163        /// Knuth's data structure.
164        first_in_prev: Option<InstIndex>,
165        /// The last node in the succeeding option, or [`None`] if this is
166        /// the spacer that comes after the last option.
167        ///
168        /// This field is an aid to traversing such option in cyclic order,
169        /// from right to left.
170        last_in_next: Option<InstIndex>,
171    },
172    /// An instance of an item in some option.
173    Instance(Instance<C>),
174}
175
176/// Visits all solutions to a given XCC problem by means of dancing links.
177///
178/// More precisely, this structure embodies an implementation of Algorithm C,
179/// as presented by D. E. Knuth in Section 7.2.2.1 of [_TAOCP_ **4B**][taocp4b],
180/// part 2, pages 87–91.
181///
182/// [XCC solver]: `crate::Solver`
183/// [taocp4b]: https://www-cs-faculty.stanford.edu/~knuth/taocp.html#vol4
184pub struct Solver<'i, I, C> {
185    /// The $N=N_1+N_2$ items, some of which are uncovered and consequently
186    /// appear in the currently active lists.
187    items: Vec<Item<'i, I>>,
188    /// The [item instances] within the vertical lists, with [spacers] between
189    /// them.
190    ///
191    /// [item instances]: `Node::Instance`
192    /// [spacers]: `Node::Spacer`
193    nodes: Vec<Node<C>>,
194    /// A stack of item instance pointers used for backtracking.
195    pointers: Vec<InstIndex>,
196}
197
198impl<'i, I: Eq, C: Eq + Copy> Solver<'i, I, C> {
199    // Problem setup routines.
200
201    /// Appends a new node to the vertical list of the specified item.
202    ///
203    /// If the item is secondary, `color` may specify the color assigned to
204    /// the item by the option (if any). Otherwise `color` must be [`None`].
205    fn append_inst(&mut self, item_ix: ItemIndex, ix: InstIndex, color: Option<C>) {
206        let item = self.item_mut(item_ix);
207        item.len += 1;
208        let above = if let Some(prev_last_ix) = item.last_option.replace(ix) {
209            // Update the `below` link of the new node's predecessor
210            // in the vertical list of `item`.
211            let prev = self.instance_mut(prev_last_ix);
212            prev.below = Some(ix);
213            Some(prev_last_ix)
214        } else {
215            // This is the first option that involves `item`.
216            item.first_option = Some(ix);
217            None
218        };
219        self.nodes.push(Node::Instance(Instance {
220            item: item_ix,
221            above,
222            below: None,
223            color,
224            wants_color: false,
225        }));
226    }
227
228    // Algorithm C routines.
229
230    /// Marks an item as covered by deleting it from the list of items remaining
231    /// to be covered (the horizontal list), and by deleting all of the options
232    /// that contain the item from the database of currently active options.
233    fn cover(&mut self, ix: ItemIndex) {
234        let item = self.item(ix);
235        let mut inst_ix = item.first_option;
236
237        // Delete `item` from the horizontal list.
238        let (left_ix, right_ix) = (item.left, item.right);
239        self.item_mut(left_ix).right = right_ix;
240        self.item_mut(right_ix).left = left_ix;
241
242        // Hide all options containing `item`, from top to bottom.
243        while let Some(ix) = inst_ix {
244            self.hide(ix);
245            inst_ix = self.instance(ix).below;
246        }
247    }
248
249    /// Hides an option that cannot appear in an exact cover for the items
250    /// remaining in the horizontal list. This step traverses the colored
251    /// siblings both to the left and to the right of the node with index `ix`,
252    /// and deletes them from their corresponding vertical lists.
253    fn hide(&mut self, ix: InstIndex) {
254        // Proceed cyclically through the nodes of the option associated with
255        // the given node, from left to right. We store the nodes of an option
256        // contiguously in the `self.nodes` arena, so their indices form a
257        // sequence of consecutive integers. The end of this sublist is
258        // delimited by a spacer node, whose `prev` link points to the node
259        // associated with the first item of the option. Thus, to visit the
260        // relevant nodes we can start from the node at index `ix` until
261        // reaching a spacer node; then we return back to the first item in
262        // the option and continue removing the nodes from their vertical lists
263        // until reaching the given index `ix`.
264        let mut cur_ix = ix.increment();
265        while cur_ix != ix {
266            cur_ix = match *self.node(cur_ix.get()) {
267                Node::Spacer { first_in_prev, .. } => {
268                    // Return to the first item in the option.
269                    first_in_prev.unwrap()
270                }
271                Node::Instance(Instance {
272                    item,
273                    above,
274                    below,
275                    wants_color,
276                    ..
277                }) => {
278                    // Ignore the node if it already has the "correct" color.
279                    if !wants_color {
280                        if let Some(above) = above {
281                            self.instance_mut(above).below = below;
282                        } else {
283                            self.item_mut(item).first_option = below;
284                        }
285                        if let Some(below) = below {
286                            self.instance_mut(below).above = above;
287                        } else {
288                            self.item_mut(item).last_option = above;
289                        }
290                        // Update the length of the vertical list.
291                        self.item_mut(item).len -= 1;
292                    }
293                    // Continue to go rightwards.
294                    cur_ix.increment()
295                }
296            };
297        }
298    }
299
300    /// Undoes the updates made by the last [covering](`Self::cover`) operation.
301    /// This step puts the item at index `ix` back into the list of items remaining
302    /// to be covered (the horizontal list), and reinserts all of the options
303    /// that contain the item into the database of currently active options.
304    fn uncover(&mut self, ix: ItemIndex) {
305        let item = self.item(ix);
306        let mut node_ix = item.first_option;
307
308        // Put back `item` into the horizontal list.
309        let (left_ix, right_ix) = (item.left, item.right);
310        self.item_mut(left_ix).right = ix;
311        self.item_mut(right_ix).left = ix;
312
313        // Unhide all options containing `item`, from top to bottom. This order
314        // may appear to be incorrect at first glance, because covering is also
315        // done from top to bottom. But the answer to exercise 7.2.2.1–2 of
316        // TAOCP shows that it is completely trustworthy.
317        while let Some(ix) = node_ix {
318            self.unhide(ix);
319            node_ix = self.instance(ix).below;
320        }
321    }
322
323    /// Undoes the updates made by the last [hiding](`Self::hide`) operation.
324    /// This step visits the colored siblings both to the left and to the right
325    /// of the node at index `ix`, and puts them back into their corresponding
326    /// vertical lists.
327    fn unhide(&mut self, ix: InstIndex) {
328        // See `Self::hide` for an explanation. There is an important difference
329        // between these two methods, however: since the first spacer cannot
330        // be referenced using a `NodeIndex` and we traverse the table of nodes
331        // in reverse order, we need to use raw indices.
332        let ix = ix.get();
333        let mut cur_ix = ix - 1;
334        while cur_ix != ix {
335            cur_ix = match self.nodes[cur_ix] {
336                Node::Spacer { last_in_next, .. } => {
337                    // Return to the last item in the option.
338                    last_in_next
339                        .expect("spacer should have a last_in_next link")
340                        .get()
341                }
342                Node::Instance(Instance {
343                    item,
344                    above,
345                    below,
346                    wants_color,
347                    ..
348                }) => {
349                    // Ignore the node if we know that it has the correct color.
350                    if !wants_color {
351                        // Reinsert `inst` into its vertical list.
352                        // SAFETY: `inst` is not a spacer, so `cur_ix > 0`.
353                        let wrapped_ix = Some(InstIndex::new(cur_ix));
354                        if let Some(above) = above {
355                            self.instance_mut(above).below = wrapped_ix;
356                        } else {
357                            self.item_mut(item).first_option = wrapped_ix;
358                        }
359                        if let Some(below) = below {
360                            self.instance_mut(below).above = wrapped_ix;
361                        } else {
362                            self.item_mut(item).last_option = wrapped_ix;
363                        }
364                        // Update the length of the vertical list.
365                        self.item_mut(item).len += 1;
366                    }
367                    // Continue to go leftwards.
368                    cur_ix - 1
369                }
370            };
371        }
372    }
373
374    /// [Covers](`Self::cover`) the item of an option, if it has no color
375    /// preference. Otherwise the given node has a color control, and we
376    /// [purify](`Self::purify`) it to remove all options with conflicting
377    /// colors from the relevant vertical lists.
378    fn commit(&mut self, ix: InstIndex) {
379        let inst = self.instance(ix);
380        if let Some(color) = inst.color {
381            // Don't purify a vertical list that has already been culled.
382            if !inst.wants_color {
383                self.purify(ix, color);
384            }
385        } else {
386            self.cover(inst.item);
387        }
388    }
389
390    /// Removes all options that are incompatible with the color constraint
391    /// imposed by the given secondary item instance (if it were present in a
392    /// solution). The items of all compatible options in the relevant vertical
393    /// list temporarily have their `wants_color` field set to `true` in order
394    /// to prevent them from being repeatedly purified (because they already
395    /// have the "correct" color).
396    fn purify(&mut self, ix: InstIndex, color: C) {
397        // We cannot use `debug_assert_eq` because `C` need not implement `Debug`.
398        debug_assert!(
399            self.instance(ix).color == Some(color),
400            "item instance has unexpected or missing color control"
401        );
402        let item_ix = self.instance(ix).item;
403        let item = self.item_mut(item_ix);
404        item.purified = true;
405        // Hide all options that contain the given item but with a color other
406        // than `color`, from top to bottom. If an item in the vertical list
407        // has the "correct" color, then mark it to avoid its repurification
408        // in the future.
409        let mut cur_ix = item.first_option;
410        while let Some(ix) = cur_ix {
411            let inst = self.instance_mut(ix);
412            if inst.color == Some(color) {
413                inst.wants_color = true; // $\texttt{COLOR}(q)\gets-1$.
414            } else {
415                self.hide(ix);
416            }
417            cur_ix = self.instance(ix).below;
418        }
419    }
420
421    /// Undoes the updates made by the last ["commit"](`Self::commit`) operation.
422    /// If the given item is primary, we cover it as in Algorithm X. Otherwise
423    /// the item instance has a color control, and we [unpurify](`Self::unpurify`)
424    /// by putting back all options with conflicting colors into the relevant
425    /// vertical lists.
426    fn uncommit(&mut self, ix: InstIndex) {
427        let inst = self.instance(ix);
428        if let Some(color) = inst.color {
429            // Don't unpurify an item that's already known to have the
430            // correct color.
431            if !inst.wants_color {
432                self.unpurify(ix, color);
433            }
434        } else {
435            self.uncover(inst.item);
436        }
437    }
438
439    /// Undoes the updates made by the last ["purify"](`Self::purify`) operation.
440    /// Puts back all the options incompatible with the given secondary item
441    /// into the corresponding vertical list, and sets back the color field
442    /// of every compatible item instance.
443    fn unpurify(&mut self, ix: InstIndex, color: C) {
444        // Again, we use `debug_assert` rather than `debug_assert_eq` because
445        // `C` might not implement `Debug`.
446        debug_assert!(
447            self.instance(ix).color == Some(color),
448            "item instance has unexpected or missing color control"
449        );
450        let item_ix = self.instance(ix).item;
451        let item = self.item_mut(item_ix);
452        item.purified = false;
453        // Unhide all options that contain the given item, from bottom to top.
454        // If a node in the vertical list has its `wants_color` field set to
455        // `true`, we need to reset it.
456        let mut cur_ix = item.last_option;
457        while let Some(ix) = cur_ix {
458            let inst = self.instance_mut(ix);
459            if inst.wants_color {
460                // $\texttt{COLOR}(q)<0$ in Knuth's description.
461                inst.wants_color = false; // $\texttt{COLOR}(q)\gets c`.
462            } else {
463                self.unhide(ix);
464            }
465            cur_ix = self.instance(ix).above;
466        }
467    }
468
469    /// Finds an active primary item $i$ for which $h(i)$ is minimum, where
470    /// $h$ is usually a heuristic function intended to reduce the amount of
471    /// branching performed by Algorithm C. In case of equality, ties are
472    /// broken by using the position of $i$ within the horizontal list of
473    /// active primary items.
474    ///
475    /// Returns `None` if all primary items have been covered.
476    fn choose_item<H>(&self, heuristic: H) -> Option<ItemIndex>
477    where
478        H: Fn(&Item<'i, I>) -> usize,
479    {
480        let mut min_h = usize::MAX;
481        let mut min_ix = None;
482        let mut cur_ix = self.primary_head().right;
483        while cur_ix != PRIMARY_HEADER {
484            let item = self.item(cur_ix);
485            let h = heuristic(item);
486            if h < min_h {
487                // If $h(i)=0$, then $i$ is surely the result.
488                if h == 0 {
489                    return Some(cur_ix);
490                }
491                min_h = h;
492                min_ix = Some(cur_ix);
493            }
494            cur_ix = item.right;
495        }
496        min_ix
497    }
498
499    /// Given a node corresponding to the covering of a particular item $i$
500    /// with some option $o$, commits all the items $\neq i$ in $o$ from
501    /// the lists of active items.
502    ///
503    /// This function is part of step C5 in Knuth's Algorithm C.
504    fn commit_items_of(&mut self, ix: InstIndex) {
505        // Commit the items cyclically from left to right.
506        let mut cur_ix = ix.increment();
507        while cur_ix != ix {
508            cur_ix = match self.node(cur_ix.get()) {
509                Node::Spacer { first_in_prev, .. } => {
510                    first_in_prev.expect("spacer should have a first_in_prev link")
511                }
512                Node::Instance(_) => {
513                    self.commit(cur_ix);
514                    cur_ix.increment()
515                }
516            }
517        }
518    }
519
520    /// Given a node corresponding to the covering of a particular item $i$
521    /// with some option $o$, uncommits all the items $\neq i$ in $o$ back
522    /// into the list of items that need to be covered.
523    ///
524    /// This function is part of step C6 in Knuth's Algorithm C.
525    fn uncommit_items_of(&mut self, ix: InstIndex) {
526        // Uncommit the items cyclically, in the opposite order as `commit_items_of`.
527        // As in `Self::unhide`, we must use raw node indices in case we visit
528        // the first spacer.
529        let ix = ix.get();
530        let mut cur_ix = ix - 1;
531        while cur_ix != ix {
532            cur_ix = match self.node(cur_ix) {
533                Node::Spacer { last_in_next, .. } => last_in_next
534                    .expect("spacer should have a last_in_next link")
535                    .get(),
536                Node::Instance(_) => {
537                    // SAFETY: `node` is not the first spacer.
538                    self.uncommit(unsafe { InstIndex::new_unchecked(cur_ix) });
539                    cur_ix - 1
540                }
541            }
542        }
543    }
544}
545
546impl<'i, I: Eq, C: Eq + Copy> crate::Solver<'i, I, C> for Solver<'i, I, C> {
547    fn new(primary: &'i [I], secondary: &'i [I]) -> Self {
548        // Construct the horizontal list.
549        let n_1 = primary.len();
550        let n = n_1 + secondary.len();
551        let last_primary_ix = ItemIndex::new(n_1);
552        let primary_head = Item::header(last_primary_ix, ItemIndex::new(1));
553        let first_secondary_ix = last_primary_ix.increment();
554        let last_secondary_ix = ItemIndex::new(if secondary.is_empty() { n + 1 } else { n });
555        let secondary_head = Item::header(last_secondary_ix, first_secondary_ix);
556        let items = primary
557            .iter()
558            .chain(secondary)
559            .enumerate()
560            .map(|(prev_ix, label)| {
561                let cur_ix = ItemIndex::new(prev_ix + 1);
562                // Ensure that no item appears twice in the horizontal list, but
563                // only check this invariant in debug mode: We don't require `T`
564                // to be bound by `Ord` nor `Hash`, so this operation needs $O(N)$
565                // steps per item.
566                debug_assert!(
567                    // We cannot use `primary[..prev_ix].contains(label)` since
568                    // `prev_ix` is out of bounds when the current item is
569                    // secondary.
570                    !primary.iter().take(prev_ix).any(|o| o == label),
571                    "item at index {cur_ix:?} is already in the primary item list"
572                );
573                debug_assert!(
574                    !secondary[..prev_ix.saturating_sub(n_1)].contains(label),
575                    "item at index {cur_ix:?} is already in the secondary item list"
576                );
577                // SAFETY: `cur_ix` does not refer to the header.
578                Item::new(label, cur_ix.decrement(), cur_ix.increment())
579            });
580        let mut items: Vec<Item<'i, I>> = iter::once(primary_head)
581            .chain(items)
582            .chain(iter::once(secondary_head))
583            .collect();
584        // Only the primary items appear in the active list:
585        if !secondary.is_empty() {
586            // 1. Link the first secondary item to the secondary header.
587            items[n_1 + 1].left = ItemIndex::new(n + 1);
588            // `items[n].right` is already $n_1+1$ by construction.
589        }
590        // 2. Link the last primary item to the primary header.
591        items[n_1].right = PRIMARY_HEADER;
592        Self {
593            items,
594            // Create the node arena, and insert the first spacer.
595            nodes: vec![Node::Spacer {
596                first_in_prev: None,
597                last_in_next: None,
598            }],
599            pointers: Vec::new(),
600        }
601    }
602
603    fn add_option<P, S>(&mut self, primary: P, secondary: S)
604    where
605        P: AsRef<[I]>,
606        S: AsRef<[(I, Option<C>)]>,
607    {
608        let primary = primary.as_ref();
609        let secondary = secondary.as_ref();
610        // We will create one item instance per item in `primary` and `secondary`,
611        // followed by a trailing spacer node.
612        self.nodes.reserve(primary.len() + secondary.len() + 1);
613        let first_inst_ix = InstIndex::new(self.nodes.len());
614        let mut inst_ix = first_inst_ix;
615        for (ix, label) in primary.iter().enumerate() {
616            // Fail if an item label appears more than once in the option.
617            debug_assert!(
618                !primary[..ix].contains(label),
619                "primary item at index {ix} can only appear once in the option"
620            );
621            let item_ix = self.find_item(label).unwrap_or_else(|| {
622                panic!("primary item at index {ix} must be in the problem's item list");
623            });
624            // Append the new node to the vertical list of `item`.
625            self.append_inst(item_ix, inst_ix, None);
626            inst_ix = inst_ix.increment();
627        }
628        for (ix, inst) in secondary.iter().enumerate() {
629            // Fail if an item label appears more than one in the option.
630            let (label, color) = inst;
631            debug_assert!(
632                !primary.contains(label) && !secondary[..ix].contains(inst),
633                "secondary item at index {ix} can only appear once in the option"
634            );
635            let item_ix = self.find_item(label).unwrap_or_else(|| {
636                panic!("secondary item at index {ix} must be in the problem's item list");
637            });
638            // Append the new node to the vertical list of `item`.
639            self.append_inst(item_ix, inst_ix, *color);
640            inst_ix = inst_ix.increment();
641        }
642        assert_ne!(first_inst_ix, inst_ix, "option must have at least one item");
643        // Link the previous spacer to the last node in the option.
644        // The first spacer cannot be referenced directly; see `InstIndex`.
645        let prev_spacer = &mut self.nodes[first_inst_ix.get() - 1];
646        if let Node::Spacer { last_in_next, .. } = prev_spacer {
647            *last_in_next = inst_ix.decrement();
648        } else {
649            panic!("the record before the first node should be a spacer");
650        }
651        // Create the next spacer, and link it to the first node in the option.
652        self.nodes.push(Node::Spacer {
653            first_in_prev: Some(first_inst_ix),
654            last_in_next: None,
655        })
656    }
657
658    fn solve<F>(mut self, mut visit: F)
659    where
660        F: FnMut(Solution<'_, 'i, I, C, Self>),
661    {
662        // The heuristic function used in step C3 to choose an active item
663        // for branching. Knuth found that selecting an item whose vertical
664        // list is of minimum length often works well in practice; this strategy
665        // is called the "minimum remaining values" (MRV) heuristic. See
666        // Section 7.2.2.3 in [_The Art of Computer Programming_, Pre-Fascicle 7A]
667        // for numerous empirical results on standard exact cover problems.
668        let branch_heuristic = |i: &Item<'i, I>| i.len;
669        'outer: loop {
670            // Try to cover as many items as possible, without backtracking.
671            loop {
672                // C3: Select an item $i$ that needs to be covered.
673                if let Some(item_ix) = self.choose_item(branch_heuristic) {
674                    // C4: Cover item $i$ using the first action option $x_l$
675                    //     in its vertical list.
676                    let item = self.item(item_ix);
677                    if let Some(inst_ix) = item.first_option {
678                        self.cover(item_ix);
679                        self.pointers.push(inst_ix);
680                        // C5: Commit the items $\neq i$ in the option that
681                        //     contains $x_l$, cyclically from left to right.
682                        self.commit_items_of(inst_ix);
683                    } else {
684                        // C7: There are no options left to cover $i$; backtrack.
685                        // Optimization: we only cover an item once we know that
686                        // there is an active option that contains it. Therefore
687                        // the original step in Knuth's algorithm translates to
688                        // a no-op here.
689                        break;
690                    }
691                } else {
692                    // C2: All primary items have been covered. Visit the solution
693                    //     given by the nodes in `self.pointers` and leave the
694                    //     current recursion level.
695                    if self.is_valid_solution() {
696                        visit(Solution {
697                            solver: &mut self,
698                            level: 0,
699                            _phantom: PhantomData,
700                        });
701                    }
702                    break;
703                }
704            }
705            // C8: Leave the current recursion level $l$ until we find an item
706            //     that can be covered.
707            while let Some(inst_ix) = self.pointers.pop() {
708                // C6: In order to cover $i$ (the item associated with $x_l$)
709                //     using another option, we need to undo the covering done
710                //     by operation C5 above. This amounts to uncommiting the
711                //     items $\neq i$ in the option that contains $x_l$,
712                //     cyclically from right to left.
713                self.uncommit_items_of(inst_ix);
714                // Also set the current pointer to the next active option
715                // in the vertical list for $i$, if any.
716                let inst = self.instance(inst_ix);
717                if let Some(below_ix) = inst.below {
718                    self.pointers.push(below_ix);
719                    // C5: Try to uncommit item $i$ with this new option and
720                    //     go back to C2.
721                    self.commit_items_of(below_ix);
722                    continue 'outer;
723                } else {
724                    // C5, C7: We have tried all options for $i$; backtrack.
725                    self.uncover(inst.item);
726                }
727            }
728            // C8: We have explored the entire search tree; terminate.
729            return;
730        }
731    }
732}
733
734impl<'i, I: Eq, C> Solver<'i, I, C> {
735    /// Returns whether [`solve`] should visit the current solution.
736    ///
737    /// A solution is valid only if the (secondary) items in the purely
738    /// secondary options are already covered by options containing at least
739    /// one primary item. This happens if the vertical lists of all active
740    /// nonpurified secondary items are empty, which in turn occurs as
741    /// the result of [`hide`] operations during the commiting of items
742    /// $\ne i$ in step C5 of Algorithm C.
743    ///
744    /// [`solve`]: crate::Solver::solve
745    /// [`hide`]: Self::hide
746    fn is_valid_solution(&self) -> bool {
747        // Traverse the horizontal list of active secondary items, checking
748        // that their corresponding vertical lists are empty (and thus have
749        // already been dealt with by primary covering). Exercise 7.2.2.1–19
750        // of TAOCP explains this procedure in more detail.
751        let secondary_head_ix = ItemIndex::new(self.items.len() - 1);
752        let secondary_head = self.secondary_head();
753        let first_active_secondary_ix = secondary_head.right;
754        let mut cur_ix = first_active_secondary_ix;
755        while cur_ix != secondary_head_ix {
756            let item = self.item(cur_ix);
757            if !item.purified && item.len > 0 {
758                return false; // Skip the solution.
759            }
760            cur_ix = item.right;
761        }
762        // We have traversed the entire list of active secondary items;
763        // visit the solution.
764        true
765    }
766
767    // Accessor methods.
768
769    /// Returns a reference to the item at the given position.
770    ///
771    /// # Panics
772    ///
773    /// This function panics if the index is out of bounds.
774    fn item(&self, ix: ItemIndex) -> &Item<'i, I> {
775        &self.items[ix.get()]
776    }
777
778    /// Returns a mutable reference to the item at the given position.
779    ///
780    /// # Panics
781    ///
782    /// This function panics if the index is out of bounds.
783    fn item_mut(&mut self, ix: ItemIndex) -> &mut Item<'i, I> {
784        &mut self.items[ix.get()]
785    }
786
787    /// Returns a reference to the head of the list of primary items
788    /// that need to be covered.
789    fn primary_head(&self) -> &Item<'i, I> {
790        self.item(PRIMARY_HEADER)
791    }
792
793    /// Returns a reference to the head of the list of secondary items
794    /// that can still be covered.
795    fn secondary_head(&self) -> &Item<'i, I> {
796        self.items.last().unwrap()
797    }
798
799    /// Search through the table of primary and secondary items to find
800    /// the node with the given label, if any.
801    fn find_item(&self, label: &I) -> Option<ItemIndex> {
802        // Remember that `T` is not bound by `Ord` nor `Hash`, so we need to
803        // search through the whole `items` list in order to find the node
804        // corresponding to `label` in the worst case.
805        let mut nodes = self.items.iter().enumerate();
806        // Skip the heads of the active item lists.
807        nodes.next();
808        nodes.next_back();
809        nodes
810            .find(|(_, item)| {
811                // SAFETY: `item` is not a header, so its label is initialized.
812                let other_label = unsafe { item.label.assume_init() };
813                label == other_label
814            })
815            .map(|(ix, _)| ItemIndex::new(ix))
816    }
817
818    /// Returns a reference to the node at the given position.
819    ///
820    /// # Panics
821    ///
822    /// This function panics if the index is out of bounds.
823    fn node(&self, ix: NodeIndex) -> &Node<C> {
824        &self.nodes[ix]
825    }
826
827    /// Returns a reference to the item instance at the given position.
828    ///
829    /// # Panics
830    ///
831    /// This function panics if the index is out of bounds, or if the node
832    /// referenced is a [spacer](`Node::Spacer`) rather than an instance.
833    fn instance(&self, ix: InstIndex) -> &Instance<C> {
834        if let Node::Instance(inst) = self.node(ix.get()) {
835            inst
836        } else {
837            panic!("node at index {ix:?} is not an item instance")
838        }
839    }
840
841    /// Returns a mutable reference to the item instance at the given position.
842    ///
843    /// # Panics
844    ///
845    /// This function panics if the index is out of bounds, or if the node
846    /// referenced is a [spacer](`Node::Spacer`) rather than an instance.
847    fn instance_mut(&mut self, ix: InstIndex) -> &mut Instance<C> {
848        if let Node::Instance(inst) = &mut self.nodes[ix.get()] {
849            inst
850        } else {
851            panic!("node at index {ix:?} is not an item instance")
852        }
853    }
854}
855
856impl<'i, I: Eq, C> crate::private::Solver<'i, I, C> for Solver<'i, I, C> {
857    fn pointer(&self, level: usize) -> Option<InstIndex> {
858        self.pointers.get(level).copied()
859    }
860
861    fn level(&self) -> usize {
862        self.pointers.len()
863    }
864
865    fn option_of(&self, ix: InstIndex, result: &mut Vec<&'i I>) {
866        result.clear();
867        let mut cur_ix = ix;
868        loop {
869            cur_ix = match self.node(cur_ix.get()) {
870                Node::Spacer { first_in_prev, .. } => {
871                    first_in_prev.expect("spacer should have a first_in_prev link")
872                }
873                Node::Instance(Instance { item, .. }) => {
874                    let item = self.item(*item);
875                    // SAFETY: `item` is a nonheader node in the item table,
876                    // so its label is initialized.
877                    result.push(unsafe { item.label.assume_init() });
878                    cur_ix.increment()
879                }
880            };
881            if cur_ix == ix {
882                break;
883            }
884        }
885    }
886}
887
888#[cfg(test)]
889mod tests {
890    use super::*;
891    use crate::indices::ItemIndex;
892    use crate::{DlSolver, Solver};
893    use std::fmt::Debug;
894
895    fn assert_eq_item<'i, I>(item: &Item<'i, I>, label: &I, left: ItemIndex, right: ItemIndex)
896    where
897        I: Debug + Eq,
898    {
899        assert_eq!(unsafe { item.label.assume_init() }, label);
900        assert_eq!(item.left, left);
901        assert_eq!(item.right, right);
902    }
903
904    #[test]
905    fn new_exact_cover_with_primary_only() {
906        let solver: DlSolver<u8, ()> = DlSolver::new(&[1, 2, 3], &[]);
907        assert_eq!(solver.items.len(), 5); // 2 headers + 3 items
908
909        let primary_header = solver.primary_head();
910        assert_eq!(primary_header.left, ItemIndex::new(3));
911        assert_eq!(primary_header.right, ItemIndex::new(1));
912
913        let one = solver.item(ItemIndex::new(1));
914        assert_eq_item(one, &1, PRIMARY_HEADER, ItemIndex::new(2));
915
916        let two = solver.item(ItemIndex::new(2));
917        assert_eq_item(two, &2, ItemIndex::new(1), ItemIndex::new(3));
918
919        let three = solver.item(ItemIndex::new(3));
920        assert_eq_item(three, &3, ItemIndex::new(2), PRIMARY_HEADER);
921
922        let secondary_header = solver.secondary_head();
923        assert_eq!(secondary_header.left, ItemIndex::new(4));
924        assert_eq!(secondary_header.right, ItemIndex::new(4));
925    }
926
927    #[test]
928    fn new_exact_cover_with_primary_and_secondary() {
929        let solver: DlSolver<char, ()> = DlSolver::new(&['a', 'b', 'c'], &['d', 'e', 'f']);
930        assert_eq!(solver.items.len(), 8); // 2 headers + 6 items
931
932        // The left link of this header points to the last primary item,
933        // because the secondary items do not appear in the active list
934        // of primary items.
935        let primary_header = solver.primary_head();
936        assert_eq!(primary_header.left, ItemIndex::new(3));
937        assert_eq!(primary_header.right, ItemIndex::new(1));
938
939        let a = solver.item(ItemIndex::new(1));
940        assert_eq_item(a, &'a', PRIMARY_HEADER, ItemIndex::new(2));
941
942        let b = solver.item(ItemIndex::new(2));
943        assert_eq_item(b, &'b', ItemIndex::new(1), ItemIndex::new(3));
944
945        // The right link of the last primary item points to the primary header.
946        let c = solver.item(ItemIndex::new(3));
947        assert_eq_item(c, &'c', ItemIndex::new(2), PRIMARY_HEADER);
948
949        // The left link of the first secondary item points to the secondary header.
950        let d = solver.item(ItemIndex::new(4));
951        assert_eq_item(d, &'d', ItemIndex::new(7), ItemIndex::new(5));
952
953        let e = solver.item(ItemIndex::new(5));
954        assert_eq_item(e, &'e', ItemIndex::new(4), ItemIndex::new(6));
955
956        // The right link of the last secondary item points to the secondary header.
957        let a = solver.item(ItemIndex::new(6));
958        assert_eq_item(a, &'f', ItemIndex::new(5), ItemIndex::new(7));
959
960        // The right link of this header points to the first secondary item.
961        let secondary_header = solver.secondary_head();
962        assert_eq!(secondary_header.left, ItemIndex::new(6));
963        assert_eq!(secondary_header.right, ItemIndex::new(4));
964    }
965
966    #[test]
967    fn new_exact_cover_without_primary_has_no_solution() {
968        // We implement the "second death" method to support purely secondary
969        // options; see `DlSolver::is_valid_solution` for details. Of course,
970        // Algorithm C visits no solutions because they are all purely secondary.
971        let mut solver = DlSolver::new(&[], &[1, 2, 3]);
972        solver.add_option([], [(1, None)]);
973        solver.add_option([], [(2, Some('A'))]);
974        solver.solve(|_| panic!("found solution with purely secondary option"));
975    }
976
977    #[test]
978    fn find_item() {
979        let solver: DlSolver<char, ()> = DlSolver::new(&['a', 'b'], &['c', 'd']);
980        assert_eq!(solver.find_item(&'a'), Some(ItemIndex::new(1)));
981        assert_eq!(solver.find_item(&'b'), Some(ItemIndex::new(2)));
982        assert_eq!(solver.find_item(&'c'), Some(ItemIndex::new(3)));
983        assert_eq!(solver.find_item(&'d'), Some(ItemIndex::new(4)));
984    }
985
986    #[test]
987    fn toy_problem_with_colors() {
988        // Example problem taken from Section 7.2.2.1 of TAOCP.
989        let primary = ['p', 'q', 'r'];
990        let secondary = ['x', 'y'];
991        let mut solver = DlSolver::new(&primary, &secondary);
992        solver.add_option(['p', 'q'], [('x', None), ('y', Some('A'))]);
993        solver.add_option(['p', 'r'], [('x', Some('A')), ('y', None)]);
994        solver.add_option(['p'], [('x', Some('B'))]);
995        solver.add_option(['q'], [('x', Some('A'))]);
996        solver.add_option(['r'], [('y', Some('B'))]);
997
998        let mut count = 0;
999        let mut option = Vec::new();
1000        solver.solve(|mut solution| {
1001            assert!(solution.next(&mut option));
1002            assert_eq!(option, &[&'q', &'x']); // x:A
1003            assert!(solution.next(&mut option));
1004            assert_eq!(option, &[&'p', &'r', &'x', &'y']); // x:A y:A
1005            assert!(!solution.next(&mut option));
1006            count += 1;
1007        });
1008        assert_eq!(count, 1);
1009    }
1010
1011    #[test]
1012    fn skips_solutions_with_purely_secondary_options() {
1013        // A colored variant of the problem from Exercise 7.2.2.1–19 of TAOCP.
1014        let primary = ['a', 'b'];
1015        let secondary = ['c', 'd', 'e', 'f', 'g'];
1016        let mut solver: DlSolver<char, ()> = DlSolver::new(&primary, &secondary);
1017        solver.add_option([], [('c', None), ('e', None)]); // purely secondary
1018        solver.add_option(['a'], [('d', None), ('g', None)]);
1019        solver.add_option(['b'], [('c', None), ('f', None)]);
1020        solver.add_option(['a'], [('d', None), ('f', None)]);
1021        solver.add_option(['b'], [('g', None)]);
1022        solver.add_option([], [('d', None), ('e', None), ('g', None)]); // purely secondary
1023
1024        let mut count = 0;
1025        let mut option = Vec::new();
1026        solver.solve(|mut solution| {
1027            assert!(solution.next(&mut option));
1028            assert_eq!(option, &[&'a', &'d', &'g']);
1029            assert!(solution.next(&mut option));
1030            assert_eq!(option, &[&'b', &'c', &'f']);
1031            assert!(!solution.next(&mut option));
1032            count += 1;
1033        });
1034        // Note that 'a d g', 'b g' is not a valid solution, because it does not
1035        // intersect the purely secondary option 'c e'.
1036        assert_eq!(count, 1);
1037    }
1038
1039    #[test]
1040    fn solutions_intersect_item_sets_of_purely_secondary_options() {
1041        let mut solver = DlSolver::new(&['a'], &['b', 'c']);
1042        solver.add_option(['a'], [('b', Some(1)), ('c', Some(2))]);
1043        solver.add_option([], [('b', Some(1))]);
1044        solver.add_option([], [('b', Some(2))]);
1045        solver.add_option([], [('c', Some(3))]);
1046        // The three purely secondary options do not appear in any solution. The
1047        // only solution to the XCC problem consists of option $a b:1 c:2$ alone,
1048        // because it intersects every purely secondary option.
1049        let mut count = 0;
1050        let mut option = Vec::new();
1051        solver.solve(|mut solution| {
1052            assert!(solution.next(&mut option));
1053            assert_eq!(option, &[&'a', &'b', &'c']);
1054            assert!(!solution.next(&mut option));
1055            count += 1;
1056        });
1057        assert_eq!(count, 1);
1058    }
1059
1060    #[test]
1061    fn skips_solutions_that_do_not_intersect_a_purely_secondary_option() {
1062        let mut solver = DlSolver::new(&['a'], &['b']);
1063        solver.add_option(['a'], []);
1064        solver.add_option([], [('b', Some(1))]);
1065        // Even if option $a$ covers every primary item exactly once, it does
1066        // not intersect the purely secondary option $b$.
1067        solver.solve(|_| panic!("found solution with purely secondary option"));
1068    }
1069}