This commit is contained in:
Andrey Tkachenko 2021-12-08 18:50:21 +04:00
parent 631d25cc3c
commit ba86c49bfb
9 changed files with 318 additions and 121 deletions

View File

@ -20,6 +20,7 @@ path = "./bin/run.rs"
bit-set = "0.4"
num-rational = { version = "0.1", default-features = false }
num-traits = "0.1"
ranges = "0.3.3"
thiserror = "1.0.30"
[badges]

View File

@ -1,10 +1,11 @@
//! All different implementation.
use std::collections::HashMap;
use std::collections::{hash_map::Entry, HashMap};
use std::rc::Rc;
use crate::{Constraint, Error, PsResult, PuzzleSearch, Val, VarToken};
#[derive(Debug)]
pub struct AllDifferent {
vars: Vec<VarToken>,
}
@ -32,7 +33,7 @@ impl AllDifferent {
}
impl Constraint for AllDifferent {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> {
fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_> {
Box::new(self.vars.iter())
}
@ -54,10 +55,11 @@ impl Constraint for AllDifferent {
for val in search.get_unassigned(var) {
match all_candidates.entry(val) {
std::collections::hash_map::Entry::Occupied(mut e) => {
Entry::Occupied(mut e) => {
e.insert(None);
}
std::collections::hash_map::Entry::Vacant(e) => {
Entry::Vacant(e) => {
e.insert(Some(var));
}
}

View File

@ -6,6 +6,7 @@ use std::rc::Rc;
use crate::{Constraint, Error, LinExpr, PsResult, PuzzleSearch, Val, VarToken};
#[derive(Debug)]
pub struct Equality {
// The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ...
eqn: LinExpr,
@ -30,11 +31,11 @@ impl Equality {
}
impl Constraint for Equality {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> {
fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_> {
Box::new(self.eqn.coef.keys())
}
fn on_assigned(&self, search: &mut PuzzleSearch, _: VarToken, _: Val) -> PsResult<()> {
fn on_assigned(&self, search: &mut PuzzleSearch, _var: VarToken, _val: Val) -> PsResult<()> {
let mut sum = self.eqn.constant;
let mut unassigned_var = None;
@ -101,23 +102,25 @@ impl Constraint for Equality {
}
let (min_val, max_val) = search.get_min_max(var)?;
let (min_bnd, max_bnd);
if coef > Ratio::zero() {
min_bnd = ((coef * Ratio::from_integer(max_val) - sum_max) / coef)
.ceil()
.to_integer();
max_bnd = ((coef * Ratio::from_integer(min_val) - sum_min) / coef)
.floor()
.to_integer();
let (min_bnd, max_bnd) = if coef > Ratio::zero() {
(
((coef * Ratio::from_integer(max_val) - sum_max) / coef)
.ceil()
.to_integer(),
((coef * Ratio::from_integer(min_val) - sum_min) / coef)
.floor()
.to_integer(),
)
} else {
min_bnd = ((coef * Ratio::from_integer(max_val) - sum_min) / coef)
.ceil()
.to_integer();
max_bnd = ((coef * Ratio::from_integer(min_val) - sum_max) / coef)
.floor()
.to_integer();
}
(
((coef * Ratio::from_integer(max_val) - sum_min) / coef)
.ceil()
.to_integer(),
((coef * Ratio::from_integer(min_val) - sum_max) / coef)
.floor()
.to_integer(),
)
};
if min_val < min_bnd || max_bnd < max_val {
let (new_min, new_max) = search.bound_candidate_range(var, min_bnd, max_bnd)?;

View File

@ -5,14 +5,15 @@
//! cannot store additional information about the state (e.g. caches)
//! in the constraint to reuse later.
use std::fmt::Debug;
use std::rc::Rc;
use crate::{PsResult, PuzzleSearch, Val, VarToken};
/// Constraint trait.
pub trait Constraint {
pub trait Constraint: Debug {
/// An iterator over the variables that are involved in the constraint.
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a>;
fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_>;
/// Applied after a variable has been assigned.
fn on_assigned(&self, _search: &mut PuzzleSearch, _var: VarToken, _val: Val) -> PsResult<()> {

View File

@ -5,6 +5,7 @@ use std::rc::Rc;
use crate::{Constraint, PsResult, PuzzleSearch, VarToken};
#[derive(Debug)]
pub struct Unify {
var1: VarToken,
var2: VarToken,
@ -25,15 +26,12 @@ impl Unify {
/// puzzle_solver::constraint::Unify::new(m, carry[3]);
/// ```
pub fn new(var1: VarToken, var2: VarToken) -> Self {
Unify {
var1,
var2,
}
Unify { var1, var2 }
}
}
impl Constraint for Unify {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> {
fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_> {
if self.var1 != self.var2 {
Box::new(iter::once(&self.var1).chain(iter::once(&self.var2)))
} else {
@ -52,10 +50,7 @@ impl Constraint for Unify {
fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>> {
let var1 = if self.var1 == from { to } else { self.var1 };
let var2 = if self.var2 == from { to } else { self.var2 };
Ok(Rc::new(Unify {
var1,
var2,
}))
Ok(Rc::new(Unify { var1, var2 }))
}
}

View File

@ -7,7 +7,9 @@ mod error;
mod linexpr;
mod puzzle;
use core::fmt;
use num_rational::Rational32;
use num_traits::Signed;
use std::collections::HashMap;
use std::ops;
@ -41,6 +43,34 @@ pub struct LinExpr {
coef: HashMap<VarToken, Coef>,
}
impl fmt::Display for LinExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.constant)?;
for (tok, coef) in self.coef.iter() {
if coef.is_negative() {
if coef.abs() == Rational32::from_integer(1) {
write!(f, " - x{}", tok.0)?;
} else {
write!(f, " - {} * x{}", coef.abs(), tok.0)?;
}
} else if coef.abs() == Rational32::from_integer(1) {
write!(f, " + x{}", tok.0)?;
} else {
write!(f, " + {} * x{}", coef, tok.0)?;
}
}
Ok(())
}
}
impl fmt::Debug for LinExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LinExpr {{ {} }}", self)
}
}
/// A result during a puzzle solution search (Err = contradiction).
pub type PsResult<T> = Result<T, Error>;

View File

@ -1,24 +1,30 @@
//! The puzzle's state and rules.
use bit_set::BitSet;
use ranges::GenericRange;
use std::cell::Cell;
use std::collections::BTreeSet;
use std::fmt;
use std::iter;
use std::mem;
use std::ops;
use std::ops::Bound;
use std::ops::RangeBounds;
use std::rc::Rc;
use crate::constraint;
use crate::Error;
use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken};
use ranges::Ranges;
/// A collection of candidates.
#[derive(Clone, Debug, Eq, PartialEq)]
enum Candidates {
None, // A variable with no candidates.
Value(Val), // A variable set to its initial value.
Set(Rc<BTreeSet<Val>>), // A variable with a list of candidates.
Range(Ranges<Val>), // A variable with candidate ranges.
}
/// The state of a variable during the solution search.
@ -75,15 +81,38 @@ impl Candidates {
Candidates::None => 0,
Candidates::Value(_) => 1,
Candidates::Set(ref rc) => rc.len(),
Candidates::Range(r) => {
let mut total = 0;
for i in r.as_slice() {
let min = match i.start_bound() {
Bound::Included(val) => *val,
Bound::Excluded(val) => val + 1,
Bound::Unbounded => unreachable!(),
};
let max = match i.end_bound() {
Bound::Included(val) => *val,
Bound::Excluded(val) => val - 1,
Bound::Unbounded => unreachable!(),
};
total += max - min + 1;
}
total as _
}
}
}
/// Get an iterator over all of the candidates of a variable.
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = Val> + 'a> {
fn iter(&self) -> Box<dyn Iterator<Item = Val> + '_> {
match self {
Candidates::None => Box::new(iter::empty()),
Candidates::Value(val) => Box::new(iter::once(*val)),
Candidates::Set(ref rc) => Box::new(rc.iter().cloned()),
Candidates::Range(range) => {
Box::new(range.as_slice().iter().map(|x| x.into_iter()).flatten())
}
}
}
}
@ -138,6 +167,21 @@ impl Puzzle {
var
}
/// Allocate a new puzzle variable, initialising it with potential
/// candidates.
///
/// # Examples
///
/// ```
/// let mut send_more_money = puzzle_solver::Puzzle::new();
/// send_more_money.new_var_with_candidates(&[0,1,2,3,4,5,6,7,8,9]);
/// ```
pub fn new_var_with_range<R: RangeBounds<Val>>(&mut self, range: R) -> VarToken {
let var = self.new_var();
self.insert_range(var, range);
var
}
/// Allocate a 1d vector of puzzle variables, each initialised to
/// have the same set of potential candidates.
///
@ -155,6 +199,27 @@ impl Puzzle {
vars
}
/// Allocate a 1d vector of puzzle variables, each initialised to
/// have the same set of potential candidates.
///
/// # Examples
///
/// ```
/// let mut send_more_money = puzzle_solver::Puzzle::new();
/// send_more_money.new_vars_with_candidates_1d(8, &[0,1,2,3,4,5,6,7,8,9]);
/// ```
pub fn new_vars_with_range_1d<R: RangeBounds<Val> + Clone>(
&mut self,
n: usize,
range: R,
) -> Vec<VarToken> {
let mut vars = Vec::with_capacity(n);
for _ in 0..n {
vars.push(self.new_var_with_range(range.clone()));
}
vars
}
/// Allocate a 2d array of puzzle variables, each initialised to
/// have the same set of potential candidates.
///
@ -177,6 +242,28 @@ impl Puzzle {
vars
}
/// Allocate a 2d array of puzzle variables, each initialised to
/// have the same set of potential candidates.
///
/// # Examples
///
/// ```
/// let mut magic_square = puzzle_solver::Puzzle::new();
/// magic_square.new_vars_with_candidates_2d(3, 3, &[1,2,3,4,5,6,7,8,9]);
/// ```
pub fn new_vars_with_range_2d<R: RangeBounds<Val> + Clone>(
self: &mut Puzzle,
width: usize,
height: usize,
range: R,
) -> Vec<Vec<VarToken>> {
let mut vars = Vec::with_capacity(height);
for _ in 0..height {
vars.push(self.new_vars_with_range_1d(width, range.clone()));
}
vars
}
/// Set a variable to a known value.
///
/// This is useful when the variable is given as part of the
@ -220,6 +307,7 @@ impl Puzzle {
match self.candidates[idx] {
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::Range(_) => panic!("attempt to insert candidates into range"),
Candidates::None => {
self.candidates[idx] = Candidates::Set(Rc::new(BTreeSet::new()));
@ -252,10 +340,9 @@ impl Puzzle {
let VarToken(idx) = var;
match self.candidates[idx] {
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::None => (),
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::Range(_) => panic!("attempt to remove candidates from range"),
Candidates::Set(ref mut rc) => {
let cs = Rc::get_mut(rc).expect("unique");
for c in candidates.iter() {
@ -282,10 +369,9 @@ impl Puzzle {
let VarToken(idx) = var;
match self.candidates[idx] {
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::None => (),
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::Range(_) => panic!("attempt to intersect candidates on the range"),
Candidates::Set(ref mut rc) => {
let cs = Rc::get_mut(rc).expect("unique");
let mut set = BTreeSet::new();
@ -295,6 +381,31 @@ impl Puzzle {
}
}
///
/// Insert range
///
///
pub fn insert_range<R: RangeBounds<Val>>(&mut self, var: VarToken, range: R) {
let VarToken(idx) = var;
match self.candidates[idx] {
ref mut x @ Candidates::None => {
*x = Candidates::Range(Ranges::from((
range.start_bound().cloned(),
range.end_bound().cloned(),
)));
}
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::Range(ref mut r) => {
r.insert(GenericRange::new_with_bounds(
range.start_bound().cloned(),
range.end_bound().cloned(),
));
}
Candidates::Set(_) => panic!("attempt to insert range on set"),
}
}
/// Add a constraint to the puzzle solution.
pub fn add_constraint<T>(&mut self, constraint: T)
where
@ -570,6 +681,28 @@ impl<'a> PuzzleSearch<'a> {
VarState::Unassigned(ref cs) => match cs {
Candidates::None => Err(Error::Default),
Candidates::Value(val) => Ok((*val, *val)),
Candidates::Range(r) => {
let slice = r.as_slice();
let first = slice.get(0).ok_or(Error::Default)?.start_bound();
let last = slice
.get(slice.len() - 1)
.ok_or(Error::Default)?
.end_bound();
let min = match first {
Bound::Included(val) => *val,
Bound::Excluded(val) => val + 1,
Bound::Unbounded => unreachable!(),
};
let max = match last {
Bound::Included(val) => *val,
Bound::Excluded(val) => val - 1,
Bound::Unbounded => unreachable!(),
};
Ok((min, max))
}
Candidates::Set(ref rc) => rc
.iter()
.cloned()
@ -587,30 +720,34 @@ impl<'a> PuzzleSearch<'a> {
pub fn set_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> {
let VarToken(idx) = var;
match &self.vars[idx] {
VarState::Assigned(v) => return bool_to_result(*v == val),
VarState::Unassigned(ref cs) => match cs {
Candidates::None => return Err(Error::Default),
Candidates::Value(v) => return bool_to_result(*v == val),
Candidates::Set(_) => (),
},
VarState::Unified(_) => (),
}
match self.vars[idx] {
VarState::Assigned(v) => bool_to_result(v == val),
VarState::Unassigned(ref mut cs) => match cs {
Candidates::None => Err(Error::Default),
Candidates::Value(v) => bool_to_result(*v == val),
Candidates::Range(ref mut r) => {
if r.contains(&val) {
*r = Ranges::from(val..=val);
self.wake.union_with(&self.constraints.wake[idx]);
if let VarState::Unified(other) = self.vars[idx] {
self.set_candidate(other, val)
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] {
if rc.contains(&val) {
let set = Rc::make_mut(rc);
set.clear();
set.insert(val);
self.wake.union_with(&self.constraints.wake[idx]);
Ok(())
} else {
Err(Error::Default)
}
} else {
unreachable!();
Ok(())
} else {
Err(Error::Default)
}
}
Candidates::Set(rc) => {
if rc.contains(&val) {
let set = Rc::make_mut(rc);
set.clear();
set.insert(val);
self.wake.union_with(&self.constraints.wake[idx]);
Ok(())
} else {
Err(Error::Default)
}
}
},
VarState::Unified(other) => self.set_candidate(other, val),
}
}
@ -618,27 +755,29 @@ impl<'a> PuzzleSearch<'a> {
pub fn remove_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> {
let VarToken(idx) = var;
match &self.vars[idx] {
VarState::Assigned(v) => return bool_to_result(*v != val),
VarState::Unassigned(ref cs) => match cs {
Candidates::None => return Err(Error::Default),
Candidates::Value(v) => return bool_to_result(*v != val),
Candidates::Set(_) => (),
},
VarState::Unified(_) => (),
}
match self.vars[idx] {
VarState::Assigned(v) => bool_to_result(v != val),
VarState::Unassigned(ref mut cs) => match cs {
Candidates::None => Err(Error::Default),
Candidates::Value(v) => bool_to_result(*v != val),
Candidates::Range(r) => {
if r.contains(&val) {
r.remove(val..=val);
self.wake.union_with(&self.constraints.wake[idx]);
}
if let VarState::Unified(other) = self.vars[idx] {
self.remove_candidate(other, val)
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] {
if rc.contains(&val) {
let set = Rc::make_mut(rc);
set.remove(&val);
self.wake.union_with(&self.constraints.wake[idx]);
}
bool_to_result(!rc.is_empty())
} else {
unreachable!();
bool_to_result(!r.is_empty())
}
Candidates::Set(rc) => {
if rc.contains(&val) {
let set = Rc::make_mut(rc);
set.remove(&val);
self.wake.union_with(&self.constraints.wake[idx]);
}
bool_to_result(!rc.is_empty())
}
},
VarState::Unified(other) => self.remove_candidate(other, val),
}
}
@ -654,51 +793,77 @@ impl<'a> PuzzleSearch<'a> {
match self.vars[idx] {
VarState::Assigned(v) => {
if min <= v && v <= max {
return Ok((v, v));
Ok((v, v))
} else {
return Err(Error::Default);
Err(Error::Default)
}
}
VarState::Unassigned(ref cs) => match cs {
Candidates::None => return Err(Error::Default),
VarState::Unassigned(ref mut cs) => match cs {
Candidates::None => Err(Error::Default),
Candidates::Value(v) => {
if min <= *v && *v <= max {
return Ok((*v, *v));
Ok((*v, *v))
} else {
return Err(Error::Default);
Err(Error::Default)
}
}
Candidates::Range(ref mut r) => {
if max < min {
return Err(Error::Default);
}
*r = r.clone().intersect(min..=max);
self.wake.union_with(&self.constraints.wake[idx]);
let slice = r.as_slice();
let first = slice.get(0).ok_or(Error::Default)?.start_bound();
let last = slice
.get(slice.len() - 1)
.ok_or(Error::Default)?
.end_bound();
let min = match first {
Bound::Included(val) => *val,
Bound::Excluded(val) => val + 1,
Bound::Unbounded => unreachable!(),
};
let max = match last {
Bound::Included(val) => *val,
Bound::Excluded(val) => val - 1,
Bound::Unbounded => unreachable!(),
};
Ok((min, max))
}
Candidates::Set(rc) => {
let &curr_min = rc.iter().min().expect("candidates");
let &curr_max = rc.iter().max().expect("candidates");
if curr_min < min || max < curr_max {
let set = Rc::make_mut(rc);
*set = set
.iter()
.filter(|&val| min <= *val && *val <= max)
.cloned()
.collect();
self.wake.union_with(&self.constraints.wake[idx]);
rc.iter()
.cloned()
.min()
.into_iter()
.zip(rc.iter().cloned().max())
.next()
.ok_or(Error::Default)
} else {
Ok((curr_min, curr_max))
}
}
Candidates::Set(_) => (),
},
VarState::Unified(_) => (),
}
if let VarState::Unified(other) = self.vars[idx] {
self.bound_candidate_range(other, min, max)
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] {
let &curr_min = rc.iter().min().expect("candidates");
let &curr_max = rc.iter().max().expect("candidates");
if curr_min < min || max < curr_max {
let set = Rc::make_mut(rc);
*set = set
.iter()
.filter(|&val| min <= *val && *val <= max)
.cloned()
.collect();
self.wake.union_with(&self.constraints.wake[idx]);
rc.iter()
.cloned()
.min()
.into_iter()
.zip(rc.iter().cloned().max())
.next()
.ok_or(Error::Default)
} else {
Ok((curr_min, curr_max))
}
} else {
unreachable!();
VarState::Unified(other) => self.bound_candidate_range(other, min, max),
}
}

View File

@ -12,7 +12,7 @@ type Board = [[Val; SIZE]; SIZE];
fn make_sudoku(board: &Board) -> (Puzzle, Vec<Vec<VarToken>>) {
let mut sys = Puzzle::new();
let vars = sys.new_vars_with_candidates_2d(SIZE, SIZE, &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
let vars = sys.new_vars_with_range_2d(SIZE, SIZE, 1..=9);
for y in 0..SIZE {
sys.all_different(&vars[y]);

View File

@ -60,8 +60,8 @@ fn make_sums(size: usize) -> Vec<Val> {
let mut v = val as usize;
while v > 0 {
count = count + (v & 1);
v = v >> 1;
count += (v & 1);
v >>= 1;
}
if count == size / 2 {