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}