Clean up: candidate manipulation returns a Result.
Functions to manipulate candidates during the solution search now always return a Result instead of sometimes returning a bool, and other times returning an Option. We opted to use Result over Option mainly to (ab)use the try! macro. In addition, it also makes it emit a warning when we forget to handle the result. (We tend to assume people don't just ignore the result and continue as normal, instead of putting the puzzle in a special contradiction detected state.)
This commit is contained in:
parent
5bb52b03f2
commit
035a8fc8a4
@ -3,7 +3,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ::{Constraint,PuzzleSearch,Val,VarToken};
|
||||
use ::{Constraint,PsResult,PuzzleSearch,Val,VarToken};
|
||||
|
||||
pub struct AllDifferent {
|
||||
vars: Vec<VarToken>,
|
||||
@ -34,18 +34,16 @@ impl Constraint for AllDifferent {
|
||||
Box::new(self.vars.iter())
|
||||
}
|
||||
|
||||
fn on_assigned(&self, search: &mut PuzzleSearch, _var: VarToken, val: Val)
|
||||
-> bool {
|
||||
for &var2 in self.vars.iter() {
|
||||
if !search.is_assigned(var2) {
|
||||
search.remove_candidate(var2, val);
|
||||
}
|
||||
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val)
|
||||
-> PsResult<()> {
|
||||
for &var2 in self.vars.iter().filter(|&v| *v != var) {
|
||||
try!(search.remove_candidate(var2, val));
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_updated(&self, search: &mut PuzzleSearch) -> bool {
|
||||
fn on_updated(&self, search: &mut PuzzleSearch) -> PsResult<()> {
|
||||
// Build a table of which values can be assigned to which variables.
|
||||
let mut num_unassigned = 0;
|
||||
let mut all_candidates = HashMap::new();
|
||||
@ -64,30 +62,30 @@ impl Constraint for AllDifferent {
|
||||
|
||||
if num_unassigned > all_candidates.len() {
|
||||
// More unassigned variables than candidates, contradiction.
|
||||
return false;
|
||||
return Err(());
|
||||
} else if num_unassigned == all_candidates.len() {
|
||||
// As many as variables as candidates.
|
||||
for (&val, &opt) in all_candidates.iter() {
|
||||
if let Some(var) = opt {
|
||||
search.set_candidate(var, val);
|
||||
try!(search.set_candidate(var, val));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn substitute(&self, from: VarToken, to: VarToken)
|
||||
-> Option<Rc<Constraint>> {
|
||||
-> PsResult<Rc<Constraint>> {
|
||||
if let Some(idx) = self.vars.iter().position(|&var| var == from) {
|
||||
if !self.vars.contains(&to) {
|
||||
let mut new_vars = self.vars.clone();
|
||||
new_vars[idx] = to;
|
||||
return Some(Rc::new(AllDifferent{ vars: new_vars }));
|
||||
return Ok(Rc::new(AllDifferent{ vars: new_vars }));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use ::{Constraint,LinExpr,PuzzleSearch,Val,VarToken};
|
||||
use ::{Constraint,LinExpr,PsResult,PuzzleSearch,Val,VarToken};
|
||||
use intdiv::IntDiv;
|
||||
|
||||
pub struct Equality {
|
||||
@ -36,7 +36,7 @@ impl Constraint for Equality {
|
||||
}
|
||||
|
||||
fn on_assigned(&self, search: &mut PuzzleSearch, _: VarToken, _: Val)
|
||||
-> bool {
|
||||
-> PsResult<()> {
|
||||
let mut sum = self.eqn.constant;
|
||||
let mut unassigned_var = None;
|
||||
|
||||
@ -47,7 +47,7 @@ impl Constraint for Equality {
|
||||
// If we find more than one unassigned variable,
|
||||
// cannot assign any other variables.
|
||||
if unassigned_var.is_some() {
|
||||
return true;
|
||||
return Ok(());
|
||||
} else {
|
||||
unassigned_var = Some((var, coef));
|
||||
}
|
||||
@ -59,25 +59,25 @@ impl Constraint for Equality {
|
||||
// sum + coef * var = 0.
|
||||
let val = -sum / coef;
|
||||
if sum + coef * val == 0 {
|
||||
search.set_candidate(var, val);
|
||||
try!(search.set_candidate(var, val));
|
||||
} else {
|
||||
return false;
|
||||
return Err(());
|
||||
}
|
||||
} else {
|
||||
if sum != 0 {
|
||||
return false;
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_updated(&self, search: &mut PuzzleSearch) -> bool {
|
||||
fn on_updated(&self, search: &mut PuzzleSearch) -> PsResult<()> {
|
||||
let mut sum_min = self.eqn.constant;
|
||||
let mut sum_max = self.eqn.constant;
|
||||
|
||||
for (&var, &coef) in self.eqn.coef.iter() {
|
||||
if let Some((min_val, max_val)) = search.get_min_max(var) {
|
||||
let (min_val, max_val) = try!(search.get_min_max(var));
|
||||
if coef > 0 {
|
||||
sum_min += coef * min_val;
|
||||
sum_max += coef * max_val;
|
||||
@ -85,9 +85,6 @@ impl Constraint for Equality {
|
||||
sum_min += coef * max_val;
|
||||
sum_max += coef * min_val;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum (maximum) of var can be bounded by summing the
|
||||
@ -98,7 +95,7 @@ impl Constraint for Equality {
|
||||
while iters > 0 {
|
||||
iters = iters - 1;
|
||||
if !(sum_min <= 0 && 0 <= sum_max) {
|
||||
return false;
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let (&var, &coef) = iter.next().expect("cycle");
|
||||
@ -106,9 +103,8 @@ impl Constraint for Equality {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((min_val, max_val)) = search.get_min_max(var) {
|
||||
let min_bnd;
|
||||
let max_bnd;
|
||||
let (min_val, max_val) = try!(search.get_min_max(var));
|
||||
let (min_bnd, max_bnd);
|
||||
|
||||
if coef > 0 {
|
||||
min_bnd = (coef * max_val - sum_max).div_round_up(coef);
|
||||
@ -119,9 +115,9 @@ impl Constraint for Equality {
|
||||
}
|
||||
|
||||
if min_val < min_bnd || max_bnd < max_val {
|
||||
search.bound_candidate_range(var, min_bnd, max_bnd);
|
||||
let (new_min, new_max)
|
||||
= try!(search.bound_candidate_range(var, min_bnd, max_bnd));
|
||||
|
||||
if let Some((new_min, new_max)) = search.get_min_max(var) {
|
||||
if coef > 0 {
|
||||
sum_min = sum_min + coef * (new_min - min_val);
|
||||
sum_max = sum_max + coef * (new_max - max_val);
|
||||
@ -129,28 +125,22 @@ impl Constraint for Equality {
|
||||
sum_min = sum_min + coef * (new_max - max_val);
|
||||
sum_max = sum_max + coef * (new_min - min_val);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
iters = self.eqn.coef.len();
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn substitute(&self, from: VarToken, to: VarToken)
|
||||
-> Option<Rc<Constraint>> {
|
||||
-> PsResult<Rc<Constraint>> {
|
||||
let mut eqn = self.eqn.clone();
|
||||
if let Some(coef) = eqn.coef.remove(&from) {
|
||||
eqn = eqn + coef * to;
|
||||
}
|
||||
|
||||
Some(Rc::new(Equality{ eqn: eqn }))
|
||||
Ok(Rc::new(Equality{ eqn: eqn }))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use ::{PuzzleSearch,Val,VarToken};
|
||||
use ::{PsResult,PuzzleSearch,Val,VarToken};
|
||||
|
||||
/// Constraint trait.
|
||||
pub trait Constraint {
|
||||
@ -15,29 +15,22 @@ pub trait Constraint {
|
||||
fn vars<'a>(&'a self) -> Box<Iterator<Item=&'a VarToken> + 'a>;
|
||||
|
||||
/// Applied after a variable has been assigned.
|
||||
///
|
||||
/// Returns true if the search should continue with these variable
|
||||
/// assignments, or false if the constraint found a contradiction.
|
||||
fn on_assigned(&self, _search: &mut PuzzleSearch, _var: VarToken, _val: Val)
|
||||
-> bool {
|
||||
true
|
||||
-> PsResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applied after a variable's candidates has been modified.
|
||||
///
|
||||
/// Returns true if the search should continue with these variable
|
||||
/// assignments, or false if the constraint found a contradiction.
|
||||
fn on_updated(&self, _search: &mut PuzzleSearch) -> bool {
|
||||
true
|
||||
fn on_updated(&self, _search: &mut PuzzleSearch) -> PsResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Substitute the "from" variable with the "to" variable.
|
||||
///
|
||||
/// Returns a new constraint with all instances of "from" replaced
|
||||
/// with "to", or None if a contradiction was found in the
|
||||
/// process.
|
||||
/// with "to", or Err if a contradiction was found.
|
||||
fn substitute(&self, from: VarToken, to: VarToken)
|
||||
-> Option<Rc<Constraint>>;
|
||||
-> PsResult<Rc<Constraint>>;
|
||||
}
|
||||
|
||||
pub use self::alldifferent::AllDifferent;
|
||||
|
@ -35,6 +35,9 @@ pub struct LinExpr {
|
||||
coef: HashMap<VarToken, Coef>,
|
||||
}
|
||||
|
||||
/// A result during a puzzle solution search (Err = contradiction).
|
||||
pub type PsResult<T> = Result<T, ()>;
|
||||
|
||||
/// A dictionary mapping puzzle variables to the solution value.
|
||||
#[derive(Debug)]
|
||||
pub struct Solution {
|
||||
|
161
src/puzzle.rs
161
src/puzzle.rs
@ -9,7 +9,7 @@ use std::ops::Index;
|
||||
use std::rc::Rc;
|
||||
use bit_set::BitSet;
|
||||
|
||||
use ::{Constraint,LinExpr,Solution,Val,VarToken};
|
||||
use ::{Constraint,LinExpr,PsResult,Solution,Val,VarToken};
|
||||
use constraint;
|
||||
|
||||
/// A collection of candidates.
|
||||
@ -219,7 +219,6 @@ impl Puzzle {
|
||||
&Candidates::Set(_) => (),
|
||||
}
|
||||
|
||||
// Why you dumb Rust?
|
||||
if let Candidates::Set(ref mut rc) = self.candidates[idx] {
|
||||
let cs = Rc::get_mut(rc).expect("unique");
|
||||
cs.extend(candidates);
|
||||
@ -417,7 +416,7 @@ impl Puzzle {
|
||||
pub fn step(&mut self) -> Option<PuzzleSearch> {
|
||||
if self.num_vars > 0 {
|
||||
let mut search = PuzzleSearch::new(self);
|
||||
if search.constrain() {
|
||||
if search.constrain().is_ok() {
|
||||
return Some(search);
|
||||
}
|
||||
}
|
||||
@ -486,90 +485,110 @@ impl<'a> PuzzleSearch<'a> {
|
||||
}
|
||||
|
||||
/// Get the minimum and maximum values for variable.
|
||||
pub fn get_min_max(&self, var: VarToken) -> Option<(Val, Val)> {
|
||||
pub fn get_min_max(&self, var: VarToken) -> PsResult<(Val, Val)> {
|
||||
let VarToken(idx) = var;
|
||||
match &self.vars[idx] {
|
||||
&VarState::Assigned(val) => Some((val, val)),
|
||||
&VarState::Assigned(val) => Ok((val, val)),
|
||||
&VarState::Unassigned(ref cs) => match cs {
|
||||
&Candidates::None => None,
|
||||
&Candidates::Value(val) => Some((val, val)),
|
||||
&Candidates::None => Err(()),
|
||||
&Candidates::Value(val) => Ok((val, val)),
|
||||
&Candidates::Set(ref rc) => {
|
||||
rc.iter().cloned().min().into_iter()
|
||||
.zip(rc.iter().cloned().max()).next()
|
||||
.ok_or(())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a variable to a known value.
|
||||
pub fn set_candidate(&mut self, var: VarToken, val: Val) {
|
||||
/// Set a variable to a value.
|
||||
pub fn set_candidate(&mut self, var: VarToken, val: Val)
|
||||
-> PsResult<()> {
|
||||
let VarToken(idx) = var;
|
||||
match &mut self.vars[idx] {
|
||||
&mut VarState::Assigned(_) => (),
|
||||
&mut VarState::Unassigned(ref mut cs) => match cs {
|
||||
&mut Candidates::None => return,
|
||||
&mut Candidates::Value(v) => {
|
||||
if v != val {
|
||||
*cs = Candidates::None;
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
}
|
||||
|
||||
match &self.vars[idx] {
|
||||
&VarState::Assigned(v) => return bool_to_result(v == val),
|
||||
&VarState::Unassigned(ref cs) => match cs {
|
||||
&Candidates::None => return Err(()),
|
||||
&Candidates::Value(v) => return bool_to_result(v == val),
|
||||
&Candidates::Set(_) => (),
|
||||
},
|
||||
&mut Candidates::Set(ref mut rc) => {
|
||||
}
|
||||
|
||||
if let &mut VarState::Unassigned(Candidates::Set(ref mut rc))
|
||||
= &mut self.vars[idx] {
|
||||
if rc.contains(&val) {
|
||||
let mut set = Rc::make_mut(rc);
|
||||
set.clear();
|
||||
set.insert(val);
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
Ok(())
|
||||
} else {
|
||||
let mut set = Rc::make_mut(rc);
|
||||
set.clear();
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
},
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a single candidate from an unassigned variable.
|
||||
pub fn remove_candidate(&mut self, var: VarToken, val: Val) {
|
||||
/// Remove a single candidate from a variable.
|
||||
pub fn remove_candidate(&mut self, var: VarToken, val: Val)
|
||||
-> PsResult<()> {
|
||||
let VarToken(idx) = var;
|
||||
if let VarState::Unassigned(ref mut cs) = self.vars[idx] {
|
||||
match cs {
|
||||
&mut Candidates::None => return,
|
||||
&mut Candidates::Value(v) => {
|
||||
if v == val {
|
||||
*cs = Candidates::None;
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
}
|
||||
|
||||
match &self.vars[idx] {
|
||||
&VarState::Assigned(v) => return bool_to_result(v != val),
|
||||
&VarState::Unassigned(ref cs) => match cs {
|
||||
&Candidates::None => return Err(()),
|
||||
&Candidates::Value(v) => return bool_to_result(v != val),
|
||||
&Candidates::Set(_) => (),
|
||||
},
|
||||
&mut Candidates::Set(ref mut rc) => {
|
||||
}
|
||||
|
||||
if let &mut VarState::Unassigned(Candidates::Set(ref mut rc))
|
||||
= &mut self.vars[idx] {
|
||||
if rc.contains(&val) {
|
||||
let mut set = Rc::make_mut(rc);
|
||||
set.remove(&val);
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
}
|
||||
},
|
||||
}
|
||||
bool_to_result(!rc.is_empty())
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
/// Bound an unassigned variable to the given range.
|
||||
pub fn bound_candidate_range(&mut self, var: VarToken, min: Val, max: Val) {
|
||||
/// Bound an variable to the given range.
|
||||
pub fn bound_candidate_range(&mut self, var: VarToken, min: Val, max: Val)
|
||||
-> PsResult<(Val, Val)> {
|
||||
let VarToken(idx) = var;
|
||||
if let VarState::Unassigned(ref mut cs) = self.vars[idx] {
|
||||
match cs {
|
||||
&mut Candidates::None => return,
|
||||
&mut Candidates::Value(v) => {
|
||||
if !(min <= v && v <= max) {
|
||||
*cs = Candidates::None;
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
}
|
||||
|
||||
match &self.vars[idx] {
|
||||
&VarState::Assigned(v) =>
|
||||
if min <= v && v <= max {
|
||||
return Ok((v, v))
|
||||
} else {
|
||||
return Err(())
|
||||
},
|
||||
&mut Candidates::Set(ref mut rc) => {
|
||||
&VarState::Unassigned(ref cs) => match cs {
|
||||
&Candidates::None => return Err(()),
|
||||
&Candidates::Value(v) =>
|
||||
if min <= v && v <= max {
|
||||
return Ok((v, v))
|
||||
} else {
|
||||
return Err(())
|
||||
},
|
||||
&Candidates::Set(_) => (),
|
||||
},
|
||||
}
|
||||
|
||||
if let &mut VarState::Unassigned(Candidates::Set(ref mut rc))
|
||||
= &mut 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 mut set = Rc::make_mut(rc);
|
||||
*set = set.iter()
|
||||
.filter(|&val| min <= *val && *val <= max)
|
||||
@ -577,14 +596,20 @@ impl<'a> PuzzleSearch<'a> {
|
||||
.collect();
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
}
|
||||
},
|
||||
rc.iter().cloned().min().into_iter()
|
||||
.zip(rc.iter().cloned().max()).next()
|
||||
.ok_or(())
|
||||
} else {
|
||||
Ok((curr_min, curr_max))
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
/// Solve the puzzle, finding up to count solutions.
|
||||
fn solve(&mut self, count: usize, solutions: &mut Vec<Solution>) {
|
||||
if !self.constrain() {
|
||||
if self.constrain().is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -605,7 +630,7 @@ impl<'a> PuzzleSearch<'a> {
|
||||
self.puzzle.num_guesses.set(num_guesses);
|
||||
|
||||
let mut new = self.clone();
|
||||
if !new.assign(idx, val) {
|
||||
if new.assign(idx, val).is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -629,29 +654,23 @@ impl<'a> PuzzleSearch<'a> {
|
||||
}
|
||||
|
||||
/// Assign a variable (given by index) to a value.
|
||||
///
|
||||
/// Returns false if a contradiction was found.
|
||||
fn assign(&mut self, idx: usize, val: Val) -> bool {
|
||||
fn assign(&mut self, idx: usize, val: Val) -> PsResult<()> {
|
||||
let var = VarToken(idx);
|
||||
self.vars[idx] = VarState::Assigned(val);
|
||||
self.wake.union_with(&self.puzzle.wake[idx]);
|
||||
|
||||
for (cidx, constraint) in self.puzzle.constraints.iter().enumerate() {
|
||||
if self.puzzle.wake[idx].contains(cidx) {
|
||||
if !constraint.on_assigned(self, var, val) {
|
||||
return false;
|
||||
}
|
||||
try!(constraint.on_assigned(self, var, val));
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Take any obvious non-choices, using the constraints to
|
||||
/// eliminate candidates. Stops when it must start guessing.
|
||||
///
|
||||
/// Returns false if a contradiction was found.
|
||||
fn constrain(&mut self) -> bool {
|
||||
fn constrain(&mut self) -> PsResult<()> {
|
||||
while !self.wake.is_empty() {
|
||||
// "Gimme" phase:
|
||||
// - abort if any variables with 0 candidates,
|
||||
@ -664,16 +683,14 @@ impl<'a> PuzzleSearch<'a> {
|
||||
let gimme = match &self.vars[idx] {
|
||||
&VarState::Assigned(_) => None,
|
||||
&VarState::Unassigned(ref cs) => match cs.len() {
|
||||
0 => return false,
|
||||
0 => return Err(()),
|
||||
1 => cs.iter().next(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(val) = gimme {
|
||||
if !self.assign(idx, val) {
|
||||
return false;
|
||||
}
|
||||
try!(self.assign(idx, val));
|
||||
last_gimme = idx;
|
||||
} else if idx == last_gimme {
|
||||
break;
|
||||
@ -686,14 +703,12 @@ impl<'a> PuzzleSearch<'a> {
|
||||
if !self.wake.is_empty() {
|
||||
let wake = mem::replace(&mut self.wake, BitSet::new());
|
||||
for cidx in wake.iter() {
|
||||
if !self.puzzle.constraints[cidx].on_updated(self) {
|
||||
return false;
|
||||
}
|
||||
try!(self.puzzle.constraints[cidx].on_updated(self));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -736,6 +751,14 @@ impl<'a> Index<VarToken> for PuzzleSearch<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn bool_to_result(cond: bool) -> PsResult<()> {
|
||||
if cond {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ::Puzzle;
|
||||
|
@ -5,7 +5,7 @@
|
||||
extern crate puzzle_solver;
|
||||
|
||||
use std::rc::Rc;
|
||||
use puzzle_solver::{Constraint,Puzzle,PuzzleSearch,Solution,Val,VarToken};
|
||||
use puzzle_solver::*;
|
||||
|
||||
struct NoDiagonal {
|
||||
vars: Vec<VarToken>,
|
||||
@ -17,22 +17,22 @@ impl Constraint for NoDiagonal {
|
||||
}
|
||||
|
||||
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val)
|
||||
-> bool {
|
||||
-> PsResult<()> {
|
||||
let y1 = self.vars.iter().position(|&v| v == var).expect("unreachable");
|
||||
for (y2, &var2) in self.vars.iter().enumerate() {
|
||||
if !search.is_assigned(var2) {
|
||||
let x1 = val;
|
||||
let dy = (y1 as Val) - (y2 as Val);
|
||||
search.remove_candidate(var2, x1 - dy);
|
||||
search.remove_candidate(var2, x1 + dy);
|
||||
try!(search.remove_candidate(var2, x1 - dy));
|
||||
try!(search.remove_candidate(var2, x1 + dy));
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn substitute(&self, _from: VarToken, _to: VarToken)
|
||||
-> Option<Rc<Constraint>> {
|
||||
-> PsResult<Rc<Constraint>> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user