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