#![doc = include_str!("../README.md")]
#![warn(clippy::pedantic)]
#![deny(unsafe_code)]
pub trait ExactCoverProblem {
fn try_next_item(&mut self) -> bool;
fn select_option_or_undo_item(&mut self) -> bool;
fn try_undo_option(&mut self) -> bool;
fn search(&mut self) -> bool {
if self.try_next_item() {
while self.select_option_or_undo_item() {
if !self.try_next_item() {
return true;
}
}
}
loop {
loop {
if !self.try_undo_option() {
return false;
}
if self.select_option_or_undo_item() {
break;
}
}
if !self.try_next_item() {
return true;
}
while self.select_option_or_undo_item() {
if !self.try_next_item() {
return true;
}
}
}
}
}
struct DoubleIndexLink {
prev: usize,
next: usize,
}
trait DoubleIndexLinkedList {
fn remove_links(&mut self, target: usize);
fn restore_links(&mut self, target: usize);
fn is_removed(&self, target: usize) -> bool;
}
impl<T> DoubleIndexLinkedList for T
where
T: core::ops::IndexMut<usize, Output = DoubleIndexLink>,
{
fn remove_links(&mut self, target: usize) {
let DoubleIndexLink { prev, next } = self[target];
self[prev].next = next;
self[next].prev = prev;
}
fn restore_links(&mut self, target: usize) {
let DoubleIndexLink { prev, next } = self[target];
self[prev].next = target;
self[next].prev = target;
}
fn is_removed(&self, target: usize) -> bool {
let DoubleIndexLink { prev, next } = self[target];
!(self[prev].next == target && self[next].prev == target)
}
}
struct LinkIterator<'a> {
list: &'a [DoubleIndexLink],
head: usize,
cursor: usize,
}
impl<'a> Iterator for LinkIterator<'a> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
Some(self.cursor).filter(|&c| c != self.head).map(|c| {
self.cursor = self.list[c].next;
c
})
}
}
impl<'a> LinkIterator<'a> {
fn from_slice(slice: &'a [DoubleIndexLink], head: usize) -> Self {
Self {
list: slice,
head,
cursor: slice[head].next,
}
}
}
#[derive(Copy, Clone)]
pub struct DlxOption(usize);
pub struct DlxBuilder {
dlx: Dlx,
}
impl DlxBuilder {
#[must_use]
pub fn new(n_primary: usize, n_secondary: usize) -> Self {
let n = n_primary.checked_add(n_secondary).expect("Too many items");
let len = n.checked_add(2).expect("Too many items");
let mut dlx = Dlx {
h_links: Vec::with_capacity(len),
v_links: Vec::with_capacity(len),
data: vec![0; len],
selected_options: Vec::new(),
current_item: None,
};
dlx.h_links.push(DoubleIndexLink {
prev: n_primary,
next: 1,
});
for i in 1..=n {
dlx.h_links.push(DoubleIndexLink {
prev: i - 1,
next: i + 1,
});
}
dlx.h_links.push(DoubleIndexLink {
prev: n,
next: n_primary + 1,
});
dlx.h_links[n_primary].next = 0;
dlx.h_links[n_primary + 1].prev = n + 1;
for i in 0..=n {
dlx.v_links.push(DoubleIndexLink { prev: i, next: i });
}
dlx.v_links.push(DoubleIndexLink { prev: 0, next: 0 });
Self { dlx }
}
pub fn add_option<'a, I: IntoIterator<Item = &'a usize>>(&mut self, option: I) -> &mut Self {
let prev_spacer = self.dlx.v_links.len() - 1;
let mut prev_item = 0; let mut current = prev_spacer;
for &item in option {
assert!(self.dlx.is_item(item));
assert!(
item > prev_item,
"option items must be unique and sorted ascending"
);
prev_item = item;
let old_bottom = self.dlx.v_links[item].prev;
self.dlx.v_links.push(DoubleIndexLink {
prev: old_bottom,
next: item,
});
self.dlx.data.push(item);
current += 1;
self.dlx.v_links[item].prev = current;
self.dlx.v_links[old_bottom].next = current;
self.dlx.data[item] += 1; }
if current == prev_spacer {
return self;
}
self.dlx.v_links[prev_spacer].next = current;
self.dlx.data.push(0); self.dlx.v_links.push(DoubleIndexLink {
prev: prev_spacer + 1,
next: 0,
});
self
}
#[must_use]
pub fn build(self) -> Dlx {
self.dlx
}
}
impl From<DlxBuilder> for Dlx {
fn from(value: DlxBuilder) -> Self {
value.build()
}
}
impl AsRef<Dlx> for DlxBuilder {
fn as_ref(&self) -> &Dlx {
&self.dlx
}
}
pub struct Dlx {
h_links: Vec<DoubleIndexLink>,
v_links: Vec<DoubleIndexLink>,
data: Vec<usize>,
selected_options: Vec<DlxOption>,
current_item: Option<usize>,
}
impl Dlx {
pub fn select_item(&mut self, item: usize) {
debug_assert!(self.current_item.is_none());
debug_assert!(self.is_item(item));
self.cover(item);
self.current_item = Some(item);
}
pub fn undo_item(&mut self) {
let item = self.current_item.expect("Current item must be set");
self.uncover(item);
self.current_item = None;
}
#[must_use]
pub fn next_option(&self, prev: Option<DlxOption>) -> Option<DlxOption> {
let current_item = self
.current_item
.expect("Current item needs to be set to see available options");
let prev = match prev {
Some(DlxOption(option)) => {
debug_assert!(self.is_option(option));
debug_assert!(self.option_covers_current_item(option));
option
}
None => current_item,
};
let next = self.v_links[prev].next;
(!self.is_item(next)).then_some(DlxOption(next))
}
pub fn select_option(&mut self, option: DlxOption) {
let option = option.0;
debug_assert!(self.is_option(option));
debug_assert!(self.option_covers_current_item(option));
self.for_other_cw(option, |dlx, i| {
dlx.cover(dlx.data[i]);
});
self.selected_options.push(DlxOption(option));
self.current_item = None;
}
pub fn try_undo_option(&mut self) -> Option<DlxOption> {
debug_assert!(self.current_item.is_none());
self.selected_options.pop().map(|DlxOption(last_option)| {
self.current_item = Some(self.data[last_option]);
self.for_other_ccw(last_option, |dlx, i| {
dlx.uncover(dlx.data[i]);
});
DlxOption(last_option)
})
}
pub fn primary_items(&self) -> impl Iterator<Item = usize> + '_ {
LinkIterator::from_slice(&self.h_links, 0)
}
#[must_use]
pub fn options_len(&self, item: usize) -> usize {
debug_assert!(self.is_item(item));
self.data[item]
}
#[must_use]
pub fn current_solution(&self) -> Option<&[DlxOption]> {
(self.h_links[0].next == 0).then_some(&self.selected_options)
}
#[must_use]
pub fn option_items(&self, option: DlxOption) -> &[usize] {
let option = option.0;
debug_assert!(self.is_option(option));
let mut spacer = option;
while self.data[spacer] > 0 {
spacer -= 1;
}
&self.data[spacer + 1..=self.v_links[spacer].next]
}
fn is_item(&self, i: usize) -> bool {
(1..self.h_links.len() - 1).contains(&i)
}
fn is_option(&self, i: usize) -> bool {
(self.h_links.len()..self.data.len()).contains(&i) && self.data[i] > 0
}
fn option_covers_current_item(&self, option: usize) -> bool {
self.current_item == Some(self.data[option])
}
fn cover(&mut self, item: usize) {
debug_assert!(self.is_item(item), "{item} must be an item");
debug_assert!(
!self.h_links.is_removed(item),
"item {item} must not already be covered"
);
let mut node = self.v_links[item].next;
while node != item {
self.hide(node);
node = self.v_links[node].next;
}
self.h_links.remove_links(item);
}
fn uncover(&mut self, item: usize) {
debug_assert!(self.is_item(item), "{item} must be an item");
debug_assert!(self.h_links.is_removed(item), "item {item} must be covered");
let mut node = self.v_links[item].prev;
while node != item {
self.unhide(node);
node = self.v_links[node].prev;
}
self.h_links.restore_links(item);
}
fn hide(&mut self, node: usize) {
self.for_other_cw(node, |dlx, i| {
dlx.v_links.remove_links(i);
let item = dlx.data[i];
dlx.data[item] -= 1;
});
}
fn unhide(&mut self, node: usize) {
self.for_other_ccw(node, |dlx, i| {
dlx.v_links.restore_links(i);
let item = dlx.data[i];
dlx.data[item] += 1;
});
}
fn for_other_cw<F>(&mut self, node: usize, mut f: F)
where
F: FnMut(&mut Self, usize),
{
let mut i = node + 1;
while i != node {
if self.data[i] == 0 {
i = self.v_links[i].prev;
} else {
f(self, i);
i += 1;
}
}
}
fn for_other_ccw<F>(&mut self, node: usize, mut f: F)
where
F: FnMut(&mut Self, usize),
{
let mut i = node - 1;
while i != node {
if self.data[i] == 0 {
i = self.v_links[i].next;
} else {
f(self, i);
i -= 1;
}
}
}
}
pub struct MrvExactCoverSearch {
dlx: Dlx,
option_cursor: Option<DlxOption>,
}
impl MrvExactCoverSearch {
#[must_use]
pub fn new(dlx: Dlx) -> Self {
let mut result = Self {
dlx,
option_cursor: None,
};
if result.dlx.current_item.is_some() {
result.select_option_or_undo_item();
}
result
}
#[must_use]
pub fn current_solution(&self) -> Option<impl IntoIterator<Item = &[usize]>> {
self.dlx
.current_solution()
.map(|options| options.iter().map(|&option| self.dlx.option_items(option)))
}
}
impl ExactCoverProblem for MrvExactCoverSearch {
fn try_next_item(&mut self) -> bool {
self.dlx
.primary_items()
.min_by_key(|&item| self.dlx.options_len(item))
.map(|item| {
self.dlx.select_item(item);
self.option_cursor = None;
})
.is_some()
}
fn select_option_or_undo_item(&mut self) -> bool {
if let Some(node) = self.dlx.next_option(self.option_cursor) {
self.dlx.select_option(node);
true
} else {
self.dlx.undo_item();
false
}
}
fn try_undo_option(&mut self) -> bool {
self.dlx
.try_undo_option()
.map(|last_option| self.option_cursor = Some(last_option))
.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn count_items_backward(links: &[DoubleIndexLink], head: usize) -> usize {
let mut n = 0;
let mut i = head;
while head != links[i].prev {
n += 1;
i = links[i].prev;
}
n
}
fn count_items_forward(links: &[DoubleIndexLink], head: usize) -> usize {
let mut n = 0;
let mut i = head;
while head != links[i].next {
n += 1;
i = links[i].next;
}
n
}
#[test]
fn test_dlx_new() {
let x = DlxBuilder::new(4, 3).dlx;
assert_eq!(count_items_forward(&x.h_links, 0), 4);
assert_eq!(count_items_backward(&x.h_links, 0), 4);
assert_eq!(count_items_forward(&x.h_links, x.h_links.len() - 1), 3);
assert_eq!(count_items_backward(&x.h_links, x.h_links.len() - 1), 3);
assert_eq!(x.data.len(), 9);
assert_eq!(x.h_links.len(), 9);
assert_eq!(x.v_links.len(), 9);
}
#[test]
fn test_dlx_empty() {
let x = DlxBuilder::new(0, 0).dlx;
assert_eq!(count_items_forward(&x.h_links, 0), 0);
assert_eq!(count_items_backward(&x.h_links, 0), 0);
assert_eq!(count_items_forward(&x.h_links, 1), 0);
assert_eq!(count_items_backward(&x.h_links, 1), 0);
let mut mrv = MrvExactCoverSearch::new(x);
assert!(!mrv.search());
assert!(mrv
.current_solution()
.unwrap()
.into_iter()
.collect::<Vec<_>>()
.is_empty());
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_dlx_knuth_example() {
let mut x = DlxBuilder::new(7, 0);
x.add_option(&[3, 5]);
x.add_option(&[1, 4, 7]);
x.add_option(&[2, 3, 6]);
x.add_option(&[1, 4, 6]);
x.add_option(&[2, 7]);
x.add_option(&[4, 5, 7]);
let mut x = x.build();
assert_eq!(x.data.len(), 31);
assert_eq!(x.v_links.len(), 31);
let collect_vlinks = |x: &Dlx| {
x.v_links
.iter()
.zip(x.data.iter())
.map(|(link, data)| (*data, link.prev, link.next))
.collect::<Vec<_>>()
};
let expected = [
(0, 0, 0),
(2, 20, 12),
(2, 24, 16),
(2, 17, 9),
(3, 27, 13),
(2, 28, 10),
(2, 22, 18),
(3, 29, 14),
(0, 0, 10),
(3, 3, 17),
(5, 5, 28),
(0, 9, 14),
(1, 1, 20),
(4, 4, 21),
(7, 7, 25),
(0, 12, 18),
(2, 2, 24),
(3, 9, 3),
(6, 6, 22),
(0, 16, 22),
(1, 12, 1),
(4, 13, 27),
(6, 18, 6),
(0, 20, 25),
(2, 16, 2),
(7, 14, 29),
(0, 24, 29),
(4, 21, 4),
(5, 10, 5),
(7, 25, 7),
(0, 27, 0),
];
assert_eq!(collect_vlinks(&x), expected);
x.select_item(1);
assert_eq!(x.primary_items().collect::<Vec<_>>(), [2, 3, 4, 5, 6, 7]);
let expected = [
(0, 0, 0),
(2, 20, 12),
(2, 24, 16),
(2, 17, 9),
(1, 27, 27),
(2, 28, 10),
(1, 18, 18),
(2, 29, 25),
(0, 0, 10),
(3, 3, 17),
(5, 5, 28),
(0, 9, 14),
(1, 1, 20),
(4, 4, 21),
(7, 7, 25),
(0, 12, 18),
(2, 2, 24),
(3, 9, 3),
(6, 6, 6),
(0, 16, 22),
(1, 12, 1),
(4, 4, 27),
(6, 18, 6),
(0, 20, 25),
(2, 16, 2),
(7, 7, 29),
(0, 24, 29),
(4, 4, 4),
(5, 10, 5),
(7, 25, 7),
(0, 27, 0),
];
assert_eq!(collect_vlinks(&x), expected);
x.select_option(DlxOption(12));
assert_eq!(x.primary_items().collect::<Vec<_>>(), [2, 3, 5, 6]);
assert_eq!(x.data[2..=6], [1, 2, 1, 1, 1]);
assert_eq!(x.v_links[2].next, 16);
assert_eq!(x.v_links[2].prev, 16);
x.select_item(2);
assert_eq!(x.current_item, Some(2));
assert_eq!(x.next_option(None).unwrap().0, 16);
assert!(x.next_option(Some(DlxOption(16))).is_none());
x.select_option(DlxOption(16));
assert_eq!(x.current_item, None);
assert_eq!(count_items_forward(&x.h_links, 0), 1);
assert_eq!(x.data[5], 0);
assert_eq!(x.data[3], 1);
assert_eq!(x.h_links[0].next, 5);
assert_eq!(x.h_links[0].prev, 5);
assert!(x.try_undo_option().is_some());
assert_eq!(x.current_item, Some(2));
x.undo_item();
assert_eq!(x.current_item, None);
assert_eq!(x.primary_items().collect::<Vec<_>>(), [2, 3, 5, 6]);
assert_eq!(x.data[2..=6], [1, 2, 1, 1, 1]);
assert_eq!(x.v_links[2].next, 16);
assert_eq!(x.v_links[2].prev, 16);
}
#[test]
fn solve_knuth_example() {
let mut x = DlxBuilder::new(7, 0);
x.add_option(&[3, 5]);
x.add_option(&[1, 4, 7]);
x.add_option(&[2, 3, 6]);
x.add_option(&[1, 4, 6]);
x.add_option(&[2, 7]);
x.add_option(&[4, 5, 7]);
let mut ec = MrvExactCoverSearch::new(x.build());
assert!(ec.search());
let solution = ec
.current_solution()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
assert_eq!(solution, [&[1, 4, 6][..], &[2, 7], &[3, 5],]);
assert!(!ec.search());
assert!(ec.current_solution().is_none());
}
}