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