Equality: bound variable range by using min/max of other variables.

magicsquare_3x3: 873 -> 63 guesses.
magicsquare_4x4: 13475456 -> 539910 guesses (all solutions).
sendmoremoney_carry: 154 -> 6 guesses.
sendmoremoney_naive: 633681 -> 4 guesses.
zebra: 453 -> no guesswork!
This commit is contained in:
David Wang 2017-03-03 07:26:05 +11:00
parent 8e62c85bf5
commit abb4cebce3
3 changed files with 133 additions and 5 deletions

View File

@ -1,6 +1,7 @@
//! Equality implementation. //! Equality implementation.
use ::{Constraint,LinExpr,PuzzleSearch,Val,VarToken}; use ::{Constraint,LinExpr,PuzzleSearch,Val,VarToken};
use intdiv::IntDiv;
pub struct Equality { pub struct Equality {
// The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ... // The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ...
@ -68,11 +69,82 @@ impl Constraint for Equality {
true true
} }
fn on_updated(&self, search: &mut PuzzleSearch) -> bool {
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) {
if coef > 0 {
sum_min += coef * min_val;
sum_max += coef * max_val;
} else {
sum_min += coef * max_val;
sum_max += coef * min_val;
}
} else {
return false;
}
}
// Minimum (maximum) of var can be bounded by summing the
// maximum (minimum) of everything else. Repeat until no
// changes further changes to the extremes found.
let mut iters = self.eqn.coef.len();
let mut iter = self.eqn.coef.iter().cycle();
while iters > 0 {
iters = iters - 1;
if !(sum_min <= 0 && 0 <= sum_max) {
return false;
}
let (&var, &coef) = iter.next().expect("cycle");
if search.is_assigned(var) {
continue;
}
if let Some((min_val, max_val)) = search.get_min_max(var) {
let min_bnd;
let max_bnd;
if coef > 0 {
min_bnd = (coef * max_val - sum_max).div_round_up(coef);
max_bnd = (coef * min_val - sum_min).div_round_down(coef);
} else {
min_bnd = (coef * max_val - sum_min).div_round_up(coef);
max_bnd = (coef * min_val - sum_max).div_round_down(coef);
}
if min_val < min_bnd || max_bnd < max_val {
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);
} else {
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
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use ::Puzzle; use ::{Puzzle,Val};
#[test] #[test]
fn test_contradiction() { fn test_contradiction() {
@ -98,4 +170,17 @@ mod tests {
assert_eq!(search[v0], 1); assert_eq!(search[v0], 1);
assert_eq!(search[v1], 3); assert_eq!(search[v1], 3);
} }
#[test]
fn test_reduce_range() {
let mut puzzle = Puzzle::new();
let v0 = puzzle.new_var_with_candidates(&[1,2,3]);
let v1 = puzzle.new_var_with_candidates(&[3,4,5]);
puzzle.equals(v0 + v1, 5);
let search = puzzle.step().expect("contradiction");
assert_eq!(search.get_unassigned(v0).collect::<Vec<Val>>(), &[1,2]);
assert_eq!(search.get_unassigned(v1).collect::<Vec<Val>>(), &[3,4]);
}
} }

View File

@ -483,6 +483,22 @@ impl<'a> PuzzleSearch<'a> {
} }
} }
/// Get the minimum and maximum values for variable.
pub fn get_min_max(&self, var: VarToken) -> Option<(Val, Val)> {
let VarToken(idx) = var;
match &self.vars[idx] {
&VarState::Assigned(val) => Some((val, val)),
&VarState::Unassigned(ref cs) => match cs {
&Candidates::None => None,
&Candidates::Value(val) => Some((val, val)),
&Candidates::Set(ref rc) => {
rc.iter().cloned().min().into_iter()
.zip(rc.iter().cloned().max()).next()
}
},
}
}
/// Set a variable to a known value. /// Set a variable to a known value.
pub fn set_candidate(&mut self, var: VarToken, val: Val) { pub fn set_candidate(&mut self, var: VarToken, val: Val) {
let VarToken(idx) = var; let VarToken(idx) = var;
@ -535,6 +551,35 @@ impl<'a> PuzzleSearch<'a> {
} }
} }
/// Bound an unassigned variable to the given range.
pub fn bound_candidate_range(&mut self, var: VarToken, min: Val, max: 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]);
}
},
&mut Candidates::Set(ref mut 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 mut set = Rc::make_mut(rc);
*set = set.iter()
.filter(|&val| min <= *val && *val <= max)
.cloned()
.collect();
self.wake.union_with(&self.puzzle.wake[idx]);
}
},
}
}
}
/// Solve the puzzle, finding up to count solutions. /// Solve the puzzle, finding up to count solutions.
fn solve(&mut self, count: usize, solutions: &mut Vec<Solution>) { fn solve(&mut self, count: usize, solutions: &mut Vec<Solution>) {
if !self.constrain() { if !self.constrain() {

View File

@ -67,18 +67,16 @@ fn sendmoremoney_carry() {
sys.equals(c3 + s + m, 10 * m + o); sys.equals(c3 + s + m, 10 * m + o);
let dict = sys.solve_unique().expect("solution"); let dict = sys.solve_unique().expect("solution");
println!("sendmoremoney_carry: {} guesses", sys.num_guesses());
print_send_more_money(&dict, &vars); print_send_more_money(&dict, &vars);
verify_send_more_money(&dict, &vars); verify_send_more_money(&dict, &vars);
println!("sendmoremoney_carry: {} guesses", sys.num_guesses());
} }
/*
#[test] #[test]
fn sendmoremoney_naive() { fn sendmoremoney_naive() {
let (mut sys, vars) = make_send_more_money(); let (mut sys, vars) = make_send_more_money();
let dict = sys.solve_unique().expect("solution"); let dict = sys.solve_unique().expect("solution");
println!("sendmoremoney_naive: {} guesses", sys.num_guesses());
print_send_more_money(&dict, &vars); print_send_more_money(&dict, &vars);
verify_send_more_money(&dict, &vars); verify_send_more_money(&dict, &vars);
println!("sendmoremoney_naive: {} guesses", sys.num_guesses());
} }
*/