Ranges
This commit is contained in:
parent
631d25cc3c
commit
ba86c49bfb
@ -20,6 +20,7 @@ path = "./bin/run.rs"
|
|||||||
bit-set = "0.4"
|
bit-set = "0.4"
|
||||||
num-rational = { version = "0.1", default-features = false }
|
num-rational = { version = "0.1", default-features = false }
|
||||||
num-traits = "0.1"
|
num-traits = "0.1"
|
||||||
|
ranges = "0.3.3"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
//! All different implementation.
|
//! All different implementation.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{Constraint, Error, PsResult, PuzzleSearch, Val, VarToken};
|
use crate::{Constraint, Error, PsResult, PuzzleSearch, Val, VarToken};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct AllDifferent {
|
pub struct AllDifferent {
|
||||||
vars: Vec<VarToken>,
|
vars: Vec<VarToken>,
|
||||||
}
|
}
|
||||||
@ -32,7 +33,7 @@ impl AllDifferent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Constraint for 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())
|
Box::new(self.vars.iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,10 +55,11 @@ impl Constraint for AllDifferent {
|
|||||||
|
|
||||||
for val in search.get_unassigned(var) {
|
for val in search.get_unassigned(var) {
|
||||||
match all_candidates.entry(val) {
|
match all_candidates.entry(val) {
|
||||||
std::collections::hash_map::Entry::Occupied(mut e) => {
|
Entry::Occupied(mut e) => {
|
||||||
e.insert(None);
|
e.insert(None);
|
||||||
}
|
}
|
||||||
std::collections::hash_map::Entry::Vacant(e) => {
|
|
||||||
|
Entry::Vacant(e) => {
|
||||||
e.insert(Some(var));
|
e.insert(Some(var));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use crate::{Constraint, Error, LinExpr, PsResult, PuzzleSearch, Val, VarToken};
|
use crate::{Constraint, Error, LinExpr, PsResult, PuzzleSearch, Val, VarToken};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Equality {
|
pub struct Equality {
|
||||||
// The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ...
|
// The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ...
|
||||||
eqn: LinExpr,
|
eqn: LinExpr,
|
||||||
@ -30,11 +31,11 @@ impl Equality {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Constraint for 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())
|
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 sum = self.eqn.constant;
|
||||||
let mut unassigned_var = None;
|
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_val, max_val) = search.get_min_max(var)?;
|
||||||
let (min_bnd, max_bnd);
|
let (min_bnd, max_bnd) = if coef > Ratio::zero() {
|
||||||
|
(
|
||||||
if coef > Ratio::zero() {
|
((coef * Ratio::from_integer(max_val) - sum_max) / coef)
|
||||||
min_bnd = ((coef * Ratio::from_integer(max_val) - sum_max) / coef)
|
|
||||||
.ceil()
|
.ceil()
|
||||||
.to_integer();
|
.to_integer(),
|
||||||
max_bnd = ((coef * Ratio::from_integer(min_val) - sum_min) / coef)
|
((coef * Ratio::from_integer(min_val) - sum_min) / coef)
|
||||||
.floor()
|
.floor()
|
||||||
.to_integer();
|
.to_integer(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
min_bnd = ((coef * Ratio::from_integer(max_val) - sum_min) / coef)
|
(
|
||||||
|
((coef * Ratio::from_integer(max_val) - sum_min) / coef)
|
||||||
.ceil()
|
.ceil()
|
||||||
.to_integer();
|
.to_integer(),
|
||||||
max_bnd = ((coef * Ratio::from_integer(min_val) - sum_max) / coef)
|
((coef * Ratio::from_integer(min_val) - sum_max) / coef)
|
||||||
.floor()
|
.floor()
|
||||||
.to_integer();
|
.to_integer(),
|
||||||
}
|
)
|
||||||
|
};
|
||||||
|
|
||||||
if min_val < min_bnd || max_bnd < max_val {
|
if min_val < min_bnd || max_bnd < max_val {
|
||||||
let (new_min, new_max) = search.bound_candidate_range(var, min_bnd, max_bnd)?;
|
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)
|
//! cannot store additional information about the state (e.g. caches)
|
||||||
//! in the constraint to reuse later.
|
//! in the constraint to reuse later.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{PsResult, PuzzleSearch, Val, VarToken};
|
use crate::{PsResult, PuzzleSearch, Val, VarToken};
|
||||||
|
|
||||||
/// Constraint trait.
|
/// Constraint trait.
|
||||||
pub trait Constraint {
|
pub trait Constraint: Debug {
|
||||||
/// An iterator over the variables that are involved in the constraint.
|
/// 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.
|
/// Applied after a variable has been assigned.
|
||||||
fn on_assigned(&self, _search: &mut PuzzleSearch, _var: VarToken, _val: Val) -> PsResult<()> {
|
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};
|
use crate::{Constraint, PsResult, PuzzleSearch, VarToken};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Unify {
|
pub struct Unify {
|
||||||
var1: VarToken,
|
var1: VarToken,
|
||||||
var2: VarToken,
|
var2: VarToken,
|
||||||
@ -25,15 +26,12 @@ impl Unify {
|
|||||||
/// puzzle_solver::constraint::Unify::new(m, carry[3]);
|
/// puzzle_solver::constraint::Unify::new(m, carry[3]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new(var1: VarToken, var2: VarToken) -> Self {
|
pub fn new(var1: VarToken, var2: VarToken) -> Self {
|
||||||
Unify {
|
Unify { var1, var2 }
|
||||||
var1,
|
|
||||||
var2,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Constraint for Unify {
|
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 {
|
if self.var1 != self.var2 {
|
||||||
Box::new(iter::once(&self.var1).chain(iter::once(&self.var2)))
|
Box::new(iter::once(&self.var1).chain(iter::once(&self.var2)))
|
||||||
} else {
|
} else {
|
||||||
@ -52,10 +50,7 @@ impl Constraint for Unify {
|
|||||||
fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>> {
|
fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>> {
|
||||||
let var1 = if self.var1 == from { to } else { self.var1 };
|
let var1 = if self.var1 == from { to } else { self.var1 };
|
||||||
let var2 = if self.var2 == from { to } else { self.var2 };
|
let var2 = if self.var2 == from { to } else { self.var2 };
|
||||||
Ok(Rc::new(Unify {
|
Ok(Rc::new(Unify { var1, var2 }))
|
||||||
var1,
|
|
||||||
var2,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
src/lib.rs
30
src/lib.rs
@ -7,7 +7,9 @@ mod error;
|
|||||||
mod linexpr;
|
mod linexpr;
|
||||||
mod puzzle;
|
mod puzzle;
|
||||||
|
|
||||||
|
use core::fmt;
|
||||||
use num_rational::Rational32;
|
use num_rational::Rational32;
|
||||||
|
use num_traits::Signed;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
|
||||||
@ -41,6 +43,34 @@ pub struct LinExpr {
|
|||||||
coef: HashMap<VarToken, Coef>,
|
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).
|
/// A result during a puzzle solution search (Err = contradiction).
|
||||||
pub type PsResult<T> = Result<T, Error>;
|
pub type PsResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
|
273
src/puzzle.rs
273
src/puzzle.rs
@ -1,24 +1,30 @@
|
|||||||
//! The puzzle's state and rules.
|
//! The puzzle's state and rules.
|
||||||
|
|
||||||
use bit_set::BitSet;
|
use bit_set::BitSet;
|
||||||
|
use ranges::GenericRange;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
|
use std::ops::Bound;
|
||||||
|
use std::ops::RangeBounds;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::constraint;
|
use crate::constraint;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken};
|
use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken};
|
||||||
|
|
||||||
|
use ranges::Ranges;
|
||||||
|
|
||||||
/// A collection of candidates.
|
/// A collection of candidates.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
enum Candidates {
|
enum Candidates {
|
||||||
None, // A variable with no candidates.
|
None, // A variable with no candidates.
|
||||||
Value(Val), // A variable set to its initial value.
|
Value(Val), // A variable set to its initial value.
|
||||||
Set(Rc<BTreeSet<Val>>), // A variable with a list of candidates.
|
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.
|
/// The state of a variable during the solution search.
|
||||||
@ -75,15 +81,38 @@ impl Candidates {
|
|||||||
Candidates::None => 0,
|
Candidates::None => 0,
|
||||||
Candidates::Value(_) => 1,
|
Candidates::Value(_) => 1,
|
||||||
Candidates::Set(ref rc) => rc.len(),
|
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.
|
/// 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 {
|
match self {
|
||||||
Candidates::None => Box::new(iter::empty()),
|
Candidates::None => Box::new(iter::empty()),
|
||||||
Candidates::Value(val) => Box::new(iter::once(*val)),
|
Candidates::Value(val) => Box::new(iter::once(*val)),
|
||||||
Candidates::Set(ref rc) => Box::new(rc.iter().cloned()),
|
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
|
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
|
/// Allocate a 1d vector of puzzle variables, each initialised to
|
||||||
/// have the same set of potential candidates.
|
/// have the same set of potential candidates.
|
||||||
///
|
///
|
||||||
@ -155,6 +199,27 @@ impl Puzzle {
|
|||||||
vars
|
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
|
/// Allocate a 2d array of puzzle variables, each initialised to
|
||||||
/// have the same set of potential candidates.
|
/// have the same set of potential candidates.
|
||||||
///
|
///
|
||||||
@ -177,6 +242,28 @@ impl Puzzle {
|
|||||||
vars
|
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.
|
/// Set a variable to a known value.
|
||||||
///
|
///
|
||||||
/// This is useful when the variable is given as part of the
|
/// This is useful when the variable is given as part of the
|
||||||
@ -220,6 +307,7 @@ impl Puzzle {
|
|||||||
|
|
||||||
match self.candidates[idx] {
|
match self.candidates[idx] {
|
||||||
Candidates::Value(_) => panic!("attempt to set fixed variable"),
|
Candidates::Value(_) => panic!("attempt to set fixed variable"),
|
||||||
|
Candidates::Range(_) => panic!("attempt to insert candidates into range"),
|
||||||
|
|
||||||
Candidates::None => {
|
Candidates::None => {
|
||||||
self.candidates[idx] = Candidates::Set(Rc::new(BTreeSet::new()));
|
self.candidates[idx] = Candidates::Set(Rc::new(BTreeSet::new()));
|
||||||
@ -252,10 +340,9 @@ impl Puzzle {
|
|||||||
let VarToken(idx) = var;
|
let VarToken(idx) = var;
|
||||||
|
|
||||||
match self.candidates[idx] {
|
match self.candidates[idx] {
|
||||||
Candidates::Value(_) => panic!("attempt to set fixed variable"),
|
|
||||||
|
|
||||||
Candidates::None => (),
|
Candidates::None => (),
|
||||||
|
Candidates::Value(_) => panic!("attempt to set fixed variable"),
|
||||||
|
Candidates::Range(_) => panic!("attempt to remove candidates from range"),
|
||||||
Candidates::Set(ref mut rc) => {
|
Candidates::Set(ref mut rc) => {
|
||||||
let cs = Rc::get_mut(rc).expect("unique");
|
let cs = Rc::get_mut(rc).expect("unique");
|
||||||
for c in candidates.iter() {
|
for c in candidates.iter() {
|
||||||
@ -282,10 +369,9 @@ impl Puzzle {
|
|||||||
let VarToken(idx) = var;
|
let VarToken(idx) = var;
|
||||||
|
|
||||||
match self.candidates[idx] {
|
match self.candidates[idx] {
|
||||||
Candidates::Value(_) => panic!("attempt to set fixed variable"),
|
|
||||||
|
|
||||||
Candidates::None => (),
|
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) => {
|
Candidates::Set(ref mut rc) => {
|
||||||
let cs = Rc::get_mut(rc).expect("unique");
|
let cs = Rc::get_mut(rc).expect("unique");
|
||||||
let mut set = BTreeSet::new();
|
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.
|
/// Add a constraint to the puzzle solution.
|
||||||
pub fn add_constraint<T>(&mut self, constraint: T)
|
pub fn add_constraint<T>(&mut self, constraint: T)
|
||||||
where
|
where
|
||||||
@ -570,6 +681,28 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
VarState::Unassigned(ref cs) => match cs {
|
VarState::Unassigned(ref cs) => match cs {
|
||||||
Candidates::None => Err(Error::Default),
|
Candidates::None => Err(Error::Default),
|
||||||
Candidates::Value(val) => Ok((*val, *val)),
|
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
|
Candidates::Set(ref rc) => rc
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
@ -587,19 +720,22 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
pub fn set_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> {
|
pub fn set_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> {
|
||||||
let VarToken(idx) = var;
|
let VarToken(idx) = var;
|
||||||
|
|
||||||
match &self.vars[idx] {
|
match self.vars[idx] {
|
||||||
VarState::Assigned(v) => return bool_to_result(*v == val),
|
VarState::Assigned(v) => bool_to_result(v == val),
|
||||||
VarState::Unassigned(ref cs) => match cs {
|
VarState::Unassigned(ref mut cs) => match cs {
|
||||||
Candidates::None => return Err(Error::Default),
|
Candidates::None => Err(Error::Default),
|
||||||
Candidates::Value(v) => return bool_to_result(*v == val),
|
Candidates::Value(v) => bool_to_result(*v == val),
|
||||||
Candidates::Set(_) => (),
|
Candidates::Range(ref mut r) => {
|
||||||
},
|
if r.contains(&val) {
|
||||||
VarState::Unified(_) => (),
|
*r = Ranges::from(val..=val);
|
||||||
}
|
self.wake.union_with(&self.constraints.wake[idx]);
|
||||||
|
|
||||||
if let VarState::Unified(other) = self.vars[idx] {
|
Ok(())
|
||||||
self.set_candidate(other, val)
|
} else {
|
||||||
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] {
|
Err(Error::Default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Candidates::Set(rc) => {
|
||||||
if rc.contains(&val) {
|
if rc.contains(&val) {
|
||||||
let set = Rc::make_mut(rc);
|
let set = Rc::make_mut(rc);
|
||||||
set.clear();
|
set.clear();
|
||||||
@ -609,8 +745,9 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
} else {
|
} else {
|
||||||
Err(Error::Default)
|
Err(Error::Default)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
unreachable!();
|
},
|
||||||
|
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<()> {
|
pub fn remove_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> {
|
||||||
let VarToken(idx) = var;
|
let VarToken(idx) = var;
|
||||||
|
|
||||||
match &self.vars[idx] {
|
match self.vars[idx] {
|
||||||
VarState::Assigned(v) => return bool_to_result(*v != val),
|
VarState::Assigned(v) => bool_to_result(v != val),
|
||||||
VarState::Unassigned(ref cs) => match cs {
|
VarState::Unassigned(ref mut cs) => match cs {
|
||||||
Candidates::None => return Err(Error::Default),
|
Candidates::None => Err(Error::Default),
|
||||||
Candidates::Value(v) => return bool_to_result(*v != val),
|
Candidates::Value(v) => bool_to_result(*v != val),
|
||||||
Candidates::Set(_) => (),
|
Candidates::Range(r) => {
|
||||||
},
|
if r.contains(&val) {
|
||||||
VarState::Unified(_) => (),
|
r.remove(val..=val);
|
||||||
|
self.wake.union_with(&self.constraints.wake[idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let VarState::Unified(other) = self.vars[idx] {
|
bool_to_result(!r.is_empty())
|
||||||
self.remove_candidate(other, val)
|
}
|
||||||
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] {
|
Candidates::Set(rc) => {
|
||||||
if rc.contains(&val) {
|
if rc.contains(&val) {
|
||||||
let set = Rc::make_mut(rc);
|
let set = Rc::make_mut(rc);
|
||||||
set.remove(&val);
|
set.remove(&val);
|
||||||
self.wake.union_with(&self.constraints.wake[idx]);
|
self.wake.union_with(&self.constraints.wake[idx]);
|
||||||
}
|
}
|
||||||
bool_to_result(!rc.is_empty())
|
bool_to_result(!rc.is_empty())
|
||||||
} else {
|
}
|
||||||
unreachable!();
|
},
|
||||||
|
VarState::Unified(other) => self.remove_candidate(other, val),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,28 +793,52 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
match self.vars[idx] {
|
match self.vars[idx] {
|
||||||
VarState::Assigned(v) => {
|
VarState::Assigned(v) => {
|
||||||
if min <= v && v <= max {
|
if min <= v && v <= max {
|
||||||
return Ok((v, v));
|
Ok((v, v))
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Default);
|
Err(Error::Default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VarState::Unassigned(ref cs) => match cs {
|
|
||||||
Candidates::None => return Err(Error::Default),
|
|
||||||
Candidates::Value(v) => {
|
|
||||||
if min <= *v && *v <= max {
|
|
||||||
return Ok((*v, *v));
|
|
||||||
} else {
|
|
||||||
return Err(Error::Default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Candidates::Set(_) => (),
|
|
||||||
},
|
|
||||||
VarState::Unified(_) => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
if let VarState::Unified(other) = self.vars[idx] {
|
VarState::Unassigned(ref mut cs) => match cs {
|
||||||
self.bound_candidate_range(other, min, max)
|
Candidates::None => Err(Error::Default),
|
||||||
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] {
|
Candidates::Value(v) => {
|
||||||
|
if min <= *v && *v <= max {
|
||||||
|
Ok((*v, *v))
|
||||||
|
} else {
|
||||||
|
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_min = rc.iter().min().expect("candidates");
|
||||||
let &curr_max = rc.iter().max().expect("candidates");
|
let &curr_max = rc.iter().max().expect("candidates");
|
||||||
|
|
||||||
@ -697,8 +860,10 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
} else {
|
} else {
|
||||||
Ok((curr_min, curr_max))
|
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>>) {
|
fn make_sudoku(board: &Board) -> (Puzzle, Vec<Vec<VarToken>>) {
|
||||||
let mut sys = Puzzle::new();
|
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 {
|
for y in 0..SIZE {
|
||||||
sys.all_different(&vars[y]);
|
sys.all_different(&vars[y]);
|
||||||
|
@ -60,8 +60,8 @@ fn make_sums(size: usize) -> Vec<Val> {
|
|||||||
let mut v = val as usize;
|
let mut v = val as usize;
|
||||||
|
|
||||||
while v > 0 {
|
while v > 0 {
|
||||||
count = count + (v & 1);
|
count += (v & 1);
|
||||||
v = v >> 1;
|
v >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if count == size / 2 {
|
if count == size / 2 {
|
||||||
|
Loading…
Reference in New Issue
Block a user