use super::Matrix;
use crate::{
types::{Color, ItemId, OptionId},
Unique,
};
use fixedbitset::FixedBitSet;
use std::collections::HashMap;
pub struct Solver<'a, T> {
matrix: &'a Matrix<T>,
available_items: FixedBitSet,
available_options: FixedBitSet,
committed_colors: HashMap<ItemId, Color>,
}
impl<'a, T> Solver<'a, T> {
#[must_use]
pub fn new(matrix: &'a Matrix<T>) -> Self {
let mut available_items = FixedBitSet::with_capacity(matrix.num_items());
available_items.set_range(0..matrix.num_items(), true);
let mut available_options = FixedBitSet::with_capacity(matrix.num_options());
available_options.set_range(0..matrix.num_options(), true);
Self {
matrix,
available_items,
available_options,
committed_colors: HashMap::new(),
}
}
pub fn solve_all(&mut self) -> Vec<Solution> {
self.solve(Limit::All)
}
pub fn solve_unique(&mut self) -> Unique<Solution> {
let mut solutions = self.solve(Limit::Max(2));
let s1 = solutions.pop();
let s2 = solutions.pop();
match (s1, s2) {
(Some(s1), Some(s2)) => Unique::Ambiguous(s1, s2),
(Some(s1), None) => Unique::One(s1),
(None, Some(_)) => unreachable!(),
(None, None) => Unique::None,
}
}
pub fn solve_once(&mut self) -> Option<Solution> {
self.solve(Limit::Max(1)).pop()
}
pub fn solve(&mut self, limit: Limit) -> Vec<Solution> {
let mut results = Vec::new();
let mut stack: Vec<(SavedState, Vec<OptionId>)> = vec![(self.save_state(), Vec::new())];
while let Some((state, mut solution)) = stack.pop() {
self.restore(state);
match self.choose_next_item() {
None => {
let cells = solution.clone();
results.push(Solution { option_ids: cells });
if limit.reached(results.len()) {
break;
}
}
Some(item) => {
self.available_items.set(item.index(), false);
let option_ids = self.cover_item_and_its_options(item);
let ss = self.save_state();
for option in option_ids {
self.restore(ss.clone());
self.commit(option);
solution.push(option);
let saved_state = self.save_state();
stack.push((saved_state, solution.clone()));
solution.pop();
}
}
}
}
results
}
fn commit(&mut self, option_id: OptionId) {
let items: Vec<_> = self
.matrix
.items_for_option(option_id)
.filter(|&(item, _)| self.available_items.contains(item.index()))
.collect();
for (item, color) in items {
match color {
None => {
self.cover_item_and_its_options(item);
}
Some(color) => {
if !self.committed_colors.contains_key(&item) {
self.purify(item, color);
}
}
}
self.available_items.set(item.index(), false);
}
}
fn cover_item_and_its_options(&mut self, item_num: ItemId) -> Vec<OptionId> {
let mut covered_options = Vec::new();
for option in self.matrix.options_for_item(item_num) {
if self.available_options.contains(option.option_id.index()) {
self.available_options.set(option.option_id.index(), false);
covered_options.push(option.option_id);
}
}
self.available_items.set(item_num.index(), false);
covered_options
}
fn purify(&mut self, item_num: ItemId, item_color: Color) {
for option in self.matrix.options_for_item(item_num) {
if option.colors.get(&item_num) == Some(&item_color) {
self.committed_colors.insert(item_num, item_color);
} else {
self.available_options.set(option.option_id.index(), false);
}
}
}
#[must_use]
fn choose_next_item(&self) -> Option<ItemId> {
let item_counts = self.count_items();
self.available_items
.ones()
.take_while(|&i| i < self.matrix.num_primary_items())
.min_by_key(|&i| item_counts[i])
.map(ItemId::new)
}
#[must_use]
fn count_items(&self) -> Vec<usize> {
let mut item_counts = vec![0; self.matrix.num_items()];
for option in self.available_options.ones().map(OptionId::new) {
for (item, _) in self.matrix.items_for_option(option) {
item_counts[item.index()] += 1;
}
}
item_counts
}
fn save_state(&self) -> SavedState {
SavedState {
available_items: self.available_items.clone(),
available_options: self.available_options.clone(),
known_correct: self.committed_colors.clone(),
}
}
fn restore(&mut self, state: SavedState) {
self.available_items = state.available_items;
self.available_options = state.available_options;
self.committed_colors = state.known_correct;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Limit {
Max(usize),
All,
}
impl Limit {
#[must_use]
pub fn reached(&self, len: usize) -> bool {
match self {
Limit::Max(n) => len >= *n,
Limit::All => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Solution {
option_ids: Vec<OptionId>,
}
impl Solution {
#[must_use]
pub fn meanings<'a, T>(&self, matrix: &'a Matrix<T>) -> Vec<&'a T> {
self.option_ids
.iter()
.map(|&i| &matrix.get_option(i).meaning)
.collect()
}
}
#[derive(Clone)]
pub struct SavedState {
available_items: FixedBitSet,
available_options: FixedBitSet,
known_correct: HashMap<ItemId, Color>,
}
impl std::fmt::Debug for SavedState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let available_items = self.available_items.ones().collect::<Vec<_>>();
let available_options = self.available_options.ones().collect::<Vec<_>>();
f.debug_struct("SavedState")
.field("available_items", &available_items)
.field("available_options", &available_options)
.field("known_correct", &self.known_correct)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_choose_next_item() {
let mut builder = Matrix::builder();
builder.add_primary_items(["a", "b", "c", "d"]);
builder.add_option(1, ["a", "b"]);
builder.add_option(2, ["a", "c"]);
builder.add_option(3, ["a", "d"]);
builder.add_option(4, ["b", "d"]);
let matrix = builder.build().unwrap();
let solver = Solver::new(&matrix);
assert_eq!(solver.count_items(), [3, 2, 1, 2]);
assert_eq!(
solver.choose_next_item(),
Some(ItemId::new(2)),
"c should be chosen because it has the lowest count"
);
}
#[test]
fn test_simple_solve() {
let mut builder = Matrix::builder();
builder.add_primary_item("a");
builder.add_primary_item("b");
builder.add_option(1, ["a"]);
builder.add_option(2, ["b"]);
let mut matrix = builder.build().unwrap();
let solutions = matrix
.solve_all()
.into_iter()
.map(|s| s.meanings(&matrix))
.collect::<Vec<_>>();
assert_eq!(solutions, [vec![&1, &2]]);
}
#[test]
fn test_simple_colored() {
let mut builder = Matrix::builder();
builder.add_primary_item("a");
builder.add_primary_item("b");
builder.add_secondary_item("c");
builder.add_option(1, ["a", "c:1"]);
builder.add_option(2, ["b", "c:2"]);
builder.add_option(3, ["a", "b", "c:3"]);
let mut matrix = builder.build().unwrap();
let solutions = matrix
.solve_all()
.into_iter()
.map(|s| s.meanings(&matrix))
.collect::<Vec<_>>();
assert_eq!(
solutions.as_slice(),
[vec![&3]],
"Should only have [3] as a solution"
);
}
}