Ranges
This commit is contained in:
parent
631d25cc3c
commit
ba86c49bfb
@ -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]
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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)?;
|
||||
|
@ -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<()> {
|
||||
|
@ -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 }))
|
||||
}
|
||||
}
|
||||
|
||||
|
30
src/lib.rs
30
src/lib.rs
@ -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>;
|
||||
|
||||
|
335
src/puzzle.rs
335
src/puzzle.rs
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user