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:
parent
8e62c85bf5
commit
abb4cebce3
@ -1,6 +1,7 @@
|
||||
//! Equality implementation.
|
||||
|
||||
use ::{Constraint,LinExpr,PuzzleSearch,Val,VarToken};
|
||||
use intdiv::IntDiv;
|
||||
|
||||
pub struct Equality {
|
||||
// The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ...
|
||||
@ -68,11 +69,82 @@ impl Constraint for Equality {
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use ::Puzzle;
|
||||
use ::{Puzzle,Val};
|
||||
|
||||
#[test]
|
||||
fn test_contradiction() {
|
||||
@ -98,4 +170,17 @@ mod tests {
|
||||
assert_eq!(search[v0], 1);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
pub fn set_candidate(&mut self, var: VarToken, val: Val) {
|
||||
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.
|
||||
fn solve(&mut self, count: usize, solutions: &mut Vec<Solution>) {
|
||||
if !self.constrain() {
|
||||
|
@ -67,18 +67,16 @@ fn sendmoremoney_carry() {
|
||||
sys.equals(c3 + s + m, 10 * m + o);
|
||||
|
||||
let dict = sys.solve_unique().expect("solution");
|
||||
println!("sendmoremoney_carry: {} guesses", sys.num_guesses());
|
||||
print_send_more_money(&dict, &vars);
|
||||
verify_send_more_money(&dict, &vars);
|
||||
println!("sendmoremoney_carry: {} guesses", sys.num_guesses());
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn sendmoremoney_naive() {
|
||||
let (mut sys, vars) = make_send_more_money();
|
||||
let dict = sys.solve_unique().expect("solution");
|
||||
println!("sendmoremoney_naive: {} guesses", sys.num_guesses());
|
||||
print_send_more_money(&dict, &vars);
|
||||
verify_send_more_money(&dict, &vars);
|
||||
println!("sendmoremoney_naive: {} guesses", sys.num_guesses());
|
||||
}
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user