This commit is contained in:
Andrey Tkachenko 2021-12-07 20:56:04 +04:00
parent 74ea9c497f
commit da22e1dfbb
20 changed files with 951 additions and 583 deletions

View File

@ -22,7 +22,9 @@ impl AllDifferent {
/// puzzle_solver::constraint::AllDifferent::new(&vars);
/// ```
pub fn new<'a, I>(vars: I) -> Self
where I: IntoIterator<Item=&'a VarToken> {
where
I: IntoIterator<Item = &'a VarToken>,
{
AllDifferent {
vars: vars.into_iter().cloned().collect(),
}
@ -34,8 +36,7 @@ impl Constraint for AllDifferent {
Box::new(self.vars.iter())
}
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val)
-> PsResult<()> {
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val) -> PsResult<()> {
for &var2 in self.vars.iter().filter(|&v| *v != var) {
r#try!(search.remove_candidate(var2, val));
}
@ -75,8 +76,7 @@ impl Constraint for AllDifferent {
Ok(())
}
fn substitute(&self, from: VarToken, to: VarToken)
-> PsResult<Rc<dyn Constraint>> {
fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>> {
if let Some(idx) = self.vars.iter().position(|&var| var == from) {
if !self.vars.contains(&to) {
let mut new_vars = self.vars.clone();

View File

@ -1,8 +1,8 @@
//! Equality implementation.
use std::rc::Rc;
use num_rational::Ratio;
use num_traits::Zero;
use std::rc::Rc;
use crate::{Constraint, LinExpr, PsResult, PuzzleSearch, Val, VarToken};
@ -25,9 +25,7 @@ impl Equality {
/// vars[0][0] + vars[0][1] + vars[0][2] - 15);
/// ```
pub fn new(eqn: LinExpr) -> Self {
Equality {
eqn: eqn,
}
Equality { eqn: eqn }
}
}
@ -36,8 +34,7 @@ impl Constraint for Equality {
Box::new(self.eqn.coef.keys())
}
fn on_assigned(&self, search: &mut PuzzleSearch, _: VarToken, _: Val)
-> PsResult<()> {
fn on_assigned(&self, search: &mut PuzzleSearch, _: VarToken, _: Val) -> PsResult<()> {
let mut sum = self.eqn.constant;
let mut unassigned_var = None;
@ -108,16 +105,24 @@ impl Constraint for Equality {
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();
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();
} 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();
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();
}
if min_val < min_bnd || max_bnd < max_val {
let (new_min, new_max)
= r#try!(search.bound_candidate_range(var, min_bnd, max_bnd));
let (new_min, new_max) =
r#try!(search.bound_candidate_range(var, min_bnd, max_bnd));
if coef > Ratio::zero() {
sum_min = sum_min + coef * Ratio::from_integer(new_min - min_val);
@ -134,8 +139,7 @@ impl Constraint for Equality {
Ok(())
}
fn substitute(&self, from: VarToken, to: VarToken)
-> PsResult<Rc<dyn Constraint>> {
fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>> {
let mut eqn = self.eqn.clone();
if let Some(coef) = eqn.coef.remove(&from) {
eqn = eqn + coef * to;

View File

@ -15,8 +15,7 @@ pub trait Constraint {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a>;
/// 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<()> {
Ok(())
}
@ -29,8 +28,7 @@ pub trait Constraint {
///
/// Returns a new constraint with all instances of "from" replaced
/// with "to", or Err if a contradiction was found.
fn substitute(&self, from: VarToken, to: VarToken)
-> PsResult<Rc<dyn Constraint>>;
fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>>;
}
pub use self::alldifferent::AllDifferent;

View File

@ -49,11 +49,13 @@ 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 var2 = if self.var2 == from { to } else { self.var2 };
Ok(Rc::new(Unify{ var1: var1, var2: var2 }))
Ok(Rc::new(Unify {
var1: var1,
var2: var2,
}))
}
}

View File

@ -5,9 +5,9 @@ extern crate bit_set;
extern crate num_rational;
extern crate num_traits;
use num_rational::Rational32;
use std::collections::HashMap;
use std::ops;
use num_rational::Rational32;
pub use crate::constraint::Constraint;
pub use crate::puzzle::Puzzle;

View File

@ -1,11 +1,11 @@
//! Linear expressions.
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::convert::From;
use std::ops::{Add,Mul,Neg,Sub};
use num_rational::{Ratio, Rational32};
use num_traits::{One, Zero};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::convert::From;
use std::ops::{Add, Mul, Neg, Sub};
use crate::{Coef, LinExpr, VarToken};
@ -13,13 +13,17 @@ macro_rules! impl_commutative_op {
($LHS:ident + $RHS:ident) => {
impl Add<$RHS> for $LHS {
type Output = LinExpr;
fn add(self, rhs: $RHS) -> Self::Output { rhs + self }
fn add(self, rhs: $RHS) -> Self::Output {
rhs + self
}
}
};
($LHS:ident * $RHS:ident) => {
impl Mul<$RHS> for $LHS {
type Output = LinExpr;
fn mul(self, rhs: $RHS) -> Self::Output { rhs * self }
fn mul(self, rhs: $RHS) -> Self::Output {
rhs * self
}
}
};
}
@ -28,9 +32,11 @@ macro_rules! impl_subtract_op {
($LHS:ident - $RHS:ident) => {
impl Sub<$RHS> for $LHS {
type Output = LinExpr;
fn sub(self, rhs: $RHS) -> Self::Output { self + (-rhs) }
fn sub(self, rhs: $RHS) -> Self::Output {
self + (-rhs)
}
}
};
}
pub trait IntoCoef: Zero {
@ -38,11 +44,15 @@ pub trait IntoCoef: Zero {
}
impl IntoCoef for i32 {
fn into_coef(self) -> Coef { Ratio::from_integer(self) }
fn into_coef(self) -> Coef {
Ratio::from_integer(self)
}
}
impl IntoCoef for Rational32 {
fn into_coef(self) -> Coef { self }
fn into_coef(self) -> Coef {
self
}
}
/*--------------------------------------------------------------*/
@ -196,7 +206,7 @@ impl Add for LinExpr {
match self.coef.entry(x2) {
Entry::Vacant(e) => {
e.insert(a2);
},
}
Entry::Occupied(mut e) => {
let new_coef = *e.get() + a2;
if new_coef.is_zero() {
@ -204,7 +214,7 @@ impl Add for LinExpr {
} else {
*e.get_mut() = new_coef;
}
},
}
}
}
@ -218,8 +228,8 @@ impl_subtract_op!(LinExpr - LinExpr);
#[cfg(test)]
mod tests {
use num_rational::Ratio;
use crate::Puzzle;
use num_rational::Ratio;
#[test]
fn test_ops() {

View File

@ -1,5 +1,6 @@
//! The puzzle's state and rules.
use bit_set::BitSet;
use std::cell::Cell;
use std::collections::BTreeSet;
use std::fmt;
@ -7,10 +8,9 @@ use std::iter;
use std::mem;
use std::ops;
use std::rc::Rc;
use bit_set::BitSet;
use crate::{Constraint,LinExpr,PsResult,Solution,Val,VarToken};
use crate::constraint;
use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken};
/// A collection of candidates.
#[derive(Clone, Debug, Eq, PartialEq)]
@ -146,8 +146,7 @@ impl Puzzle {
/// 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_candidates_1d(&mut self, n: usize, candidates: &[Val])
-> Vec<VarToken> {
pub fn new_vars_with_candidates_1d(&mut self, n: usize, candidates: &[Val]) -> Vec<VarToken> {
let mut vars = Vec::with_capacity(n);
for _ in 0..n {
vars.push(self.new_var_with_candidates(candidates));
@ -164,9 +163,12 @@ impl Puzzle {
/// 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_candidates_2d(self: &mut Puzzle,
width: usize, height: usize, candidates: &[Val])
-> Vec<Vec<VarToken>> {
pub fn new_vars_with_candidates_2d(
self: &mut Puzzle,
width: usize,
height: usize,
candidates: &[Val],
) -> Vec<Vec<VarToken>> {
let mut vars = Vec::with_capacity(height);
for _ in 0..height {
vars.push(self.new_vars_with_candidates_1d(width, candidates));
@ -216,12 +218,11 @@ impl Puzzle {
let VarToken(idx) = var;
match &self.candidates[idx] {
&Candidates::Value(_) =>
panic!("attempt to set fixed variable"),
&Candidates::Value(_) => panic!("attempt to set fixed variable"),
&Candidates::None => {
self.candidates[idx] = Candidates::Set(Rc::new(BTreeSet::new()));
},
}
&Candidates::Set(_) => (),
}
@ -250,8 +251,7 @@ impl Puzzle {
let VarToken(idx) = var;
match self.candidates[idx] {
Candidates::Value(_) =>
panic!("attempt to set fixed variable"),
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::None => (),
@ -260,7 +260,7 @@ impl Puzzle {
for c in candidates.iter() {
cs.remove(c);
}
},
}
}
}
@ -281,8 +281,7 @@ impl Puzzle {
let VarToken(idx) = var;
match self.candidates[idx] {
Candidates::Value(_) =>
panic!("attempt to set fixed variable"),
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::None => (),
@ -291,13 +290,15 @@ impl Puzzle {
let mut set = BTreeSet::new();
set.extend(candidates);
*cs = cs.intersection(&set).cloned().collect();
},
}
}
}
/// Add a constraint to the puzzle solution.
pub fn add_constraint<T>(&mut self, constraint: T)
where T: Constraint + 'static {
where
T: Constraint + 'static,
{
self.constraints.push(Rc::new(constraint));
}
@ -313,7 +314,9 @@ impl Puzzle {
/// send_more_money.all_different(&vars);
/// ```
pub fn all_different<'a, I>(&mut self, vars: I)
where I: IntoIterator<Item=&'a VarToken> {
where
I: IntoIterator<Item = &'a VarToken>,
{
self.add_constraint(constraint::AllDifferent::new(vars));
}
@ -329,7 +332,10 @@ impl Puzzle {
/// magic_square.equals(vars[0][0] + vars[0][1] + vars[0][2], 15);
/// ```
pub fn equals<L, R>(&mut self, lhs: L, rhs: R)
where L: Into<LinExpr>, R: Into<LinExpr> {
where
L: Into<LinExpr>,
R: Into<LinExpr>,
{
self.add_constraint(constraint::Equality::new(lhs.into() - rhs.into()));
}
@ -479,8 +485,7 @@ impl PuzzleConstraints {
}
/// Determine which variables wake up which constraints.
fn init_wake(constraints: &Vec<Rc<dyn Constraint>>, num_vars: usize)
-> Vec<BitSet> {
fn init_wake(constraints: &Vec<Rc<dyn Constraint>>, num_vars: usize) -> Vec<BitSet> {
let mut wake = vec![BitSet::new(); num_vars];
for cidx in 0..constraints.len() {
for &VarToken(idx) in constraints[cidx].vars() {
@ -498,8 +503,11 @@ impl<'a> PuzzleSearch<'a> {
/// Allocate a new puzzle searcher.
fn new(puzzle: &'a Puzzle) -> Self {
let constraints = PuzzleConstraints::new(puzzle);
let vars = puzzle.candidates.iter().map(|cs|
VarState::Unassigned(cs.clone())).collect();
let vars = puzzle
.candidates
.iter()
.map(|cs| VarState::Unassigned(cs.clone()))
.collect();
let mut wake = BitSet::new();
for cidx in 0..constraints.constraints.len() {
@ -538,8 +546,7 @@ impl<'a> PuzzleSearch<'a> {
}
/// Get an iterator over the candidates to an unassigned variable.
pub fn get_unassigned(&'a self, var: VarToken)
-> Box<dyn Iterator<Item=Val> + 'a> {
pub fn get_unassigned(&'a self, var: VarToken) -> Box<dyn Iterator<Item = Val> + 'a> {
let VarToken(idx) = var;
match &self.vars[idx] {
&VarState::Assigned(_) => Box::new(iter::empty()),
@ -556,19 +563,21 @@ impl<'a> PuzzleSearch<'a> {
&VarState::Unassigned(ref cs) => match cs {
&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(())
}
&Candidates::Set(ref rc) => rc
.iter()
.cloned()
.min()
.into_iter()
.zip(rc.iter().cloned().max())
.next()
.ok_or(()),
},
&VarState::Unified(other) => self.get_min_max(other),
}
}
/// Set a variable to a value.
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;
match &self.vars[idx] {
@ -583,8 +592,7 @@ impl<'a> PuzzleSearch<'a> {
if let &VarState::Unified(other) = &self.vars[idx] {
self.set_candidate(other, val)
} else if let &mut VarState::Unassigned(Candidates::Set(ref mut rc))
= &mut self.vars[idx] {
} else 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();
@ -600,8 +608,7 @@ impl<'a> PuzzleSearch<'a> {
}
/// Remove a single candidate from a variable.
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;
match &self.vars[idx] {
@ -616,8 +623,7 @@ impl<'a> PuzzleSearch<'a> {
if let &VarState::Unified(other) = &self.vars[idx] {
self.remove_candidate(other, val)
} else if let &mut VarState::Unassigned(Candidates::Set(ref mut rc))
= &mut self.vars[idx] {
} else 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);
@ -630,25 +636,31 @@ impl<'a> PuzzleSearch<'a> {
}
/// Bound an variable to the given range.
pub fn bound_candidate_range(&mut self, var: VarToken, min: Val, max: Val)
-> PsResult<(Val, Val)> {
pub fn bound_candidate_range(
&mut self,
var: VarToken,
min: Val,
max: Val,
) -> PsResult<(Val, Val)> {
let VarToken(idx) = var;
match &self.vars[idx] {
&VarState::Assigned(v) =>
&VarState::Assigned(v) => {
if min <= v && v <= max {
return Ok((v, v))
return Ok((v, v));
} else {
return Err(())
},
return Err(());
}
}
&VarState::Unassigned(ref cs) => match cs {
&Candidates::None => return Err(()),
&Candidates::Value(v) =>
&Candidates::Value(v) => {
if min <= v && v <= max {
return Ok((v, v))
return Ok((v, v));
} else {
return Err(())
},
return Err(());
}
}
&Candidates::Set(_) => (),
},
&VarState::Unified(_) => (),
@ -656,22 +668,26 @@ impl<'a> PuzzleSearch<'a> {
if let &VarState::Unified(other) = &self.vars[idx] {
self.bound_candidate_range(other, min, max)
} else if let &mut VarState::Unassigned(Candidates::Set(ref mut rc))
= &mut self.vars[idx] {
} else 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()
*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()
rc.iter()
.cloned()
.min()
.into_iter()
.zip(rc.iter().cloned().max())
.next()
.ok_or(())
} else {
Ok((curr_min, curr_max))
@ -687,8 +703,11 @@ impl<'a> PuzzleSearch<'a> {
return;
}
let next_unassigned = self.vars.iter().enumerate().min_by_key(
|&(_, vs)| match vs {
let next_unassigned = self
.vars
.iter()
.enumerate()
.min_by_key(|&(_, vs)| match vs {
&VarState::Unassigned(ref cs) => cs.len(),
_ => ::std::usize::MAX,
});
@ -716,8 +735,9 @@ impl<'a> PuzzleSearch<'a> {
}
} else {
// No unassigned variables remaining.
let vars = (0..self.puzzle.num_vars).map(|idx|
self[VarToken(idx)]).collect();
let vars = (0..self.puzzle.num_vars)
.map(|idx| self[VarToken(idx)])
.collect();
solutions.push(Solution { vars: vars });
}
}
@ -784,8 +804,7 @@ impl<'a> PuzzleSearch<'a> {
}
/// Unify the "from" variable with the "to" variable.
pub fn unify(&mut self, from: VarToken, to: VarToken)
-> PsResult<()> {
pub fn unify(&mut self, from: VarToken, to: VarToken) -> PsResult<()> {
if from == to {
return Ok(());
}
@ -796,19 +815,19 @@ impl<'a> PuzzleSearch<'a> {
let VarToken(search) = from;
let VarToken(replace) = to;
match (&self.vars[search], &self.vars[replace]) {
(&VarState::Assigned(val1), &VarState::Assigned(val2)) =>
return bool_to_result(val1 == val2),
(&VarState::Assigned(val1), &VarState::Assigned(val2)) => {
return bool_to_result(val1 == val2)
}
// Unified variables should have been substituted out previously.
(&VarState::Unified(_), _) | (_, &VarState::Unified(_)) =>
panic!("internal representation corrupt"),
(&VarState::Unified(_), _) | (_, &VarState::Unified(_)) => {
panic!("internal representation corrupt")
}
(&VarState::Unassigned(_), &VarState::Assigned(_)) =>
(to, from),
(&VarState::Unassigned(_), &VarState::Assigned(_)) => (to, from),
(&VarState::Assigned(_), &VarState::Unassigned(_))
| (&VarState::Unassigned(_), &VarState::Unassigned(_)) =>
(from, to),
| (&VarState::Unassigned(_), &VarState::Unassigned(_)) => (from, to),
}
};
@ -825,9 +844,11 @@ impl<'a> PuzzleSearch<'a> {
if let &VarState::Assigned(val) = &self.vars[search] {
r#try!(self.set_candidate(to, val));
} else {
if let (&mut VarState::Unassigned(Candidates::Set(ref mut rc1)),
&mut VarState::Unassigned(Candidates::Set(ref mut rc2)))
= get_two_mut(&mut self.vars, search, replace) {
if let (
&mut VarState::Unassigned(Candidates::Set(ref mut rc1)),
&mut VarState::Unassigned(Candidates::Set(ref mut rc2)),
) = get_two_mut(&mut self.vars, search, replace)
{
*rc2 = Rc::new(rc2.intersection(rc1).cloned().collect());
if rc2.is_empty() {
return Err(());
@ -848,16 +869,16 @@ impl<'a> fmt::Debug for PuzzleSearch<'a> {
match var {
&VarState::Assigned(val) => {
write!(f, " var {}: {}", idx, val)?;
},
}
&VarState::Unassigned(ref cs) => {
write!(f, " var {}:", idx)?;
for val in cs.iter() {
write!(f, " {}", val)?;
}
},
}
&VarState::Unified(VarToken(other)) => {
write!(f, " var {}: var {}", idx, other)?;
},
}
}
}
write!(f, "}}")?;
@ -891,8 +912,7 @@ fn bool_to_result(cond: bool) -> PsResult<()> {
}
}
fn get_two_mut<'a, T>(slice: &'a mut [T], a: usize, b: usize)
-> (&'a mut T, &'a mut T) {
fn get_two_mut<'a, T>(slice: &'a mut [T], a: usize, b: usize) -> (&'a mut T, &'a mut T) {
assert!(a != b);
if a < b {
let (mut l, mut r) = slice.split_at_mut(b);

View File

@ -40,9 +40,15 @@ fn make_hidato(board: &Board) -> (Puzzle, Vec<VarToken>) {
let stride = WIDTH as Val;
let deltas = [
-stride - 1, -stride, -stride + 1,
-1, 1,
stride - 1, stride, stride + 1 ];
-stride - 1,
-stride,
-stride + 1,
-1,
1,
stride - 1,
stride,
stride + 1,
];
for i in 1..vars.len() {
let step = sys.new_var_with_candidates(&deltas);
@ -91,7 +97,8 @@ fn hidato_wikipedia() {
[27, 0, 0, 0, 9, 0, 1, NA],
[NA, NA, 0, 0, 18, 0, 0, NA],
[NA, NA, NA, NA, 0, 7, 0, 0],
[ NA, NA, NA, NA, NA, NA, 5, 0 ] ];
[NA, NA, NA, NA, NA, NA, 5, 0],
];
let expected = [
[32, 33, 35, 36, 37, NA, NA, NA],
@ -101,7 +108,8 @@ fn hidato_wikipedia() {
[27, 28, 14, 19, 9, 10, 1, NA],
[NA, NA, 15, 16, 18, 8, 2, NA],
[NA, NA, NA, NA, 17, 7, 6, 3],
[ NA, NA, NA, NA, NA, NA, 5, 4 ] ];
[NA, NA, NA, NA, NA, NA, 5, 4],
];
let (mut sys, vars) = make_hidato(&puzzle);
let dict = sys.solve_any().expect("solution");

View File

@ -12,8 +12,18 @@ type Board = [[Val; SIZE]; SIZE];
type KakuroVars = Vec<Vec<Option<VarToken>>>;
enum Rule {
H{ total: Val, y: usize, x1: usize, x2: usize },
V{ total: Val, x: usize, y1: usize, y2: usize },
H {
total: Val,
y: usize,
x1: usize,
x2: usize,
},
V {
total: Val,
x: usize,
y1: usize,
y2: usize,
},
}
fn make_kakuro(board: &[Rule]) -> (Puzzle, KakuroVars) {
@ -40,7 +50,10 @@ fn make_kakuro(board: &[Rule]) -> (Puzzle, KakuroVars) {
}
sys.all_different(&vec);
sys.equals(total, vec.iter().fold(LinExpr::from(0), |sum, &var| sum + var));
sys.equals(
total,
vec.iter().fold(LinExpr::from(0), |sum, &var| sum + var),
);
}
(sys, vars)
@ -71,21 +84,150 @@ fn verify_kakuro(dict: &Solution, vars: &KakuroVars, expected: &Board) {
#[test]
fn kakuro_wikipedia() {
let puzzle = [
Rule::H{ total:16, y:0, x1:0, x2:1 }, Rule::H{ total:24, y:0, x1:4, x2:6},
Rule::H{ total:17, y:1, x1:0, x2:1 }, Rule::H{ total:29, y:1, x1:3, x2:6},
Rule::H{ total:35, y:2, x1:0, x2:4 },
Rule::H{ total: 7, y:3, x1:1, x2:2 }, Rule::H{ total: 8, y:3, x1:4, x2:5},
Rule::H{ total:16, y:4, x1:2, x2:6 },
Rule::H{ total:21, y:5, x1:0, x2:3 }, Rule::H{ total: 5, y:5, x1:5, x2:6},
Rule::H{ total: 6, y:6, x1:0, x2:2 }, Rule::H{ total: 3, y:6, x1:5, x2:6},
Rule::V{ total:23, x:0, y1:0, y2:2 }, Rule::V{ total:11, x:0, y1:5, y2:6},
Rule::V{ total:30, x:1, y1:0, y2:3 }, Rule::V{ total:10, x:1, y1:5, y2:6},
Rule::V{ total:15, x:2, y1:2, y2:6 },
Rule::V{ total:17, x:3, y1:1, y2:2 }, Rule::V{ total: 7, x:3, y1:4, y2:5},
Rule::V{ total:27, x:4, y1:0, y2:4 },
Rule::V{ total:12, x:5, y1:0, y2:1 }, Rule::V{ total:12, x:5, y1:3, y2:6},
Rule::V{ total:16, x:6, y1:0, y2:1 }, Rule::V{ total: 7, x:6, y1:4, y2:6},
Rule::H {
total: 16,
y: 0,
x1: 0,
x2: 1,
},
Rule::H {
total: 24,
y: 0,
x1: 4,
x2: 6,
},
Rule::H {
total: 17,
y: 1,
x1: 0,
x2: 1,
},
Rule::H {
total: 29,
y: 1,
x1: 3,
x2: 6,
},
Rule::H {
total: 35,
y: 2,
x1: 0,
x2: 4,
},
Rule::H {
total: 7,
y: 3,
x1: 1,
x2: 2,
},
Rule::H {
total: 8,
y: 3,
x1: 4,
x2: 5,
},
Rule::H {
total: 16,
y: 4,
x1: 2,
x2: 6,
},
Rule::H {
total: 21,
y: 5,
x1: 0,
x2: 3,
},
Rule::H {
total: 5,
y: 5,
x1: 5,
x2: 6,
},
Rule::H {
total: 6,
y: 6,
x1: 0,
x2: 2,
},
Rule::H {
total: 3,
y: 6,
x1: 5,
x2: 6,
},
Rule::V {
total: 23,
x: 0,
y1: 0,
y2: 2,
},
Rule::V {
total: 11,
x: 0,
y1: 5,
y2: 6,
},
Rule::V {
total: 30,
x: 1,
y1: 0,
y2: 3,
},
Rule::V {
total: 10,
x: 1,
y1: 5,
y2: 6,
},
Rule::V {
total: 15,
x: 2,
y1: 2,
y2: 6,
},
Rule::V {
total: 17,
x: 3,
y1: 1,
y2: 2,
},
Rule::V {
total: 7,
x: 3,
y1: 4,
y2: 5,
},
Rule::V {
total: 27,
x: 4,
y1: 0,
y2: 4,
},
Rule::V {
total: 12,
x: 5,
y1: 0,
y2: 1,
},
Rule::V {
total: 12,
x: 5,
y1: 3,
y2: 6,
},
Rule::V {
total: 16,
x: 6,
y1: 0,
y2: 1,
},
Rule::V {
total: 7,
x: 6,
y1: 4,
y2: 6,
},
];
let expected = [
@ -95,7 +237,8 @@ fn kakuro_wikipedia() {
[X, 6, 1, X, 2, 6, X],
[X, X, 4, 6, 1, 3, 2],
[8, 9, 3, 1, X, 1, 4],
[ 3, 1, 2, X, X, 2, 1 ] ];
[3, 1, 2, X, X, 2, 1],
];
let (mut sys, vars) = make_kakuro(&puzzle);
let dict = sys.solve_any().expect("solution");

View File

@ -26,12 +26,16 @@ fn make_killer_sudoku(board: &[(Val, Vec<Point>)]) -> (Puzzle, Vec<Vec<VarToken>
for block in 0..SIZE {
let x0 = SQRT_SIZE * (block % SQRT_SIZE);
let y0 = SQRT_SIZE * (block / SQRT_SIZE);
sys.all_different((0..SIZE).map(|n|
&vars[y0 + (n / SQRT_SIZE)][x0 + (n % SQRT_SIZE)]));
sys.all_different((0..SIZE).map(|n| &vars[y0 + (n / SQRT_SIZE)][x0 + (n % SQRT_SIZE)]));
}
for &(total, ref points) in board.iter() {
sys.equals(total, points.iter().fold(LinExpr::from(0), |sum, &(x,y)| sum + vars[y][x]));
sys.equals(
total,
points
.iter()
.fold(LinExpr::from(0), |sum, &(x, y)| sum + vars[y][x]),
);
}
(sys, vars)
@ -44,9 +48,11 @@ fn print_sudoku(dict: &Solution, vars: &Vec<Vec<VarToken>>) {
}
for x in 0..SIZE {
print!("{}{}",
print!(
"{}{}",
if x % SQRT_SIZE == 0 { " " } else { "" },
dict[vars[y][x]]);
dict[vars[y][x]]
);
}
println!();
}
@ -98,14 +104,13 @@ fn killersudoku_wikipedia() {
[2, 1, 5, 6, 4, 7, 3, 9, 8],
[3, 6, 8, 9, 5, 2, 1, 7, 4],
[7, 9, 4, 3, 8, 1, 6, 5, 2],
[5, 8, 6, 2, 7, 4, 9, 3, 1],
[1, 4, 2, 5, 9, 3, 8, 6, 7],
[9, 7, 3, 8, 1, 6, 4, 2, 5],
[8, 2, 1, 7, 3, 9, 5, 4, 6],
[6, 5, 9, 4, 2, 8, 7, 1, 3],
[ 4,3,7, 1,6,5, 2,8,9 ] ];
[4, 3, 7, 1, 6, 5, 2, 8, 9],
];
let (mut sys, vars) = make_killer_sudoku(&puzzle);
let dict = sys.solve_any().expect("solution");

View File

@ -19,16 +19,28 @@ fn make_magic_square(n: usize) -> (Puzzle, Vec<Vec<VarToken>>, VarToken) {
sys.all_different(vars.iter().flat_map(|it| it));
for y in 0..n {
sys.equals(total, vars[y].iter().fold(LinExpr::from(0), |sum, &x| sum + x));
sys.equals(
total,
vars[y].iter().fold(LinExpr::from(0), |sum, &x| sum + x),
);
}
for x in 0..n {
sys.equals(total, vars.iter().fold(LinExpr::from(0), |sum, row| sum + row[x]));
sys.equals(
total,
vars.iter().fold(LinExpr::from(0), |sum, row| sum + row[x]),
);
}
{
sys.equals(total, (0..n).fold(LinExpr::from(0), |sum, i| sum + vars[i][i]));
sys.equals(total, (0..n).fold(LinExpr::from(0), |sum, i| sum + vars[i][n - i - 1]));
sys.equals(
total,
(0..n).fold(LinExpr::from(0), |sum, i| sum + vars[i][i]),
);
sys.equals(
total,
(0..n).fold(LinExpr::from(0), |sum, i| sum + vars[i][n - i - 1]),
);
}
// Sum of all digits = sum of all rows (columns) = total * n.

View File

@ -4,9 +4,9 @@
extern crate puzzle_solver;
use puzzle_solver::*;
use std::collections::HashMap;
use std::rc::Rc;
use puzzle_solver::*;
const WIDTH: usize = 20;
const HEIGHT: usize = 20;
@ -45,11 +45,14 @@ impl Nonogram {
}
}
fn autofill(&self,
trial: &mut Vec<Val>, pos: usize, rule_idx: usize,
fn autofill(
&self,
trial: &mut Vec<Val>,
pos: usize,
rule_idx: usize,
accum: &mut Vec<Val>,
cache: &mut HashMap<(usize, usize), AutoFillResult>)
-> AutoFillResult {
cache: &mut HashMap<(usize, usize), AutoFillResult>,
) -> AutoFillResult {
assert!(pos <= trial.len() && rule_idx <= self.rule.len());
let key = (pos, rule_idx);
@ -65,8 +68,10 @@ impl Nonogram {
*a = *a | *t;
}
if accum.iter().all(|&t|
t < FLAG_UNKNOWN || t == FLAG_UNKNOWN | FLAG_ON | FLAG_OFF) {
if accum
.iter()
.all(|&t| t < FLAG_UNKNOWN || t == FLAG_UNKNOWN | FLAG_ON | FLAG_OFF)
{
return AutoFillResult::SearchEnded;
}
@ -75,9 +80,8 @@ impl Nonogram {
// Not enough space.
if rule_idx < self.rule.len() {
let required_space
= self.rule[rule_idx..].iter().sum::<usize>()
+ (self.rule.len() - rule_idx - 1);
let required_space =
self.rule[rule_idx..].iter().sum::<usize>() + (self.rule.len() - rule_idx - 1);
if pos + required_space > trial.len() {
return AutoFillResult::Conflict;
@ -102,7 +106,8 @@ impl Nonogram {
// Try filled.
if trial[pos] != FLAG_OFF
&& rule_idx < self.rule.len()
&& pos + self.rule[rule_idx] <= trial.len() {
&& pos + self.rule[rule_idx] <= trial.len()
{
let mut ok = true;
let mut pos = pos;
@ -159,29 +164,28 @@ impl Constraint for Nonogram {
match self.autofill(&mut trial, 0, 0, &mut accum, &mut cache) {
AutoFillResult::Conflict => return Err(()),
AutoFillResult::SearchEnded => (),
AutoFillResult::SolutionFound =>
AutoFillResult::SolutionFound => {
for idx in 0..self.vars.len() {
if accum[idx] == FLAG_UNKNOWN | FLAG_OFF {
r#try!(search.set_candidate(self.vars[idx], 0));
} else if accum[idx] == FLAG_UNKNOWN | FLAG_ON {
r#try!(search.set_candidate(self.vars[idx], 1));
}
},
}
}
}
Ok(())
}
fn substitute(&self, _search: VarToken, _replace: VarToken)
-> PsResult<Rc<dyn Constraint>> {
fn substitute(&self, _search: VarToken, _replace: VarToken) -> PsResult<Rc<dyn Constraint>> {
unimplemented!();
}
}
/*--------------------------------------------------------------*/
fn make_nonogram(rows: &[Vec<usize>], cols: &[Vec<usize>])
-> (Puzzle, Vec<Vec<VarToken>>) {
fn make_nonogram(rows: &[Vec<usize>], cols: &[Vec<usize>]) -> (Puzzle, Vec<Vec<VarToken>>) {
let mut sys = Puzzle::new();
let (w, h) = (cols.len(), rows.len());
let vars = sys.new_vars_with_candidates_2d(w, h, &[0, 1]);
@ -193,7 +197,8 @@ fn make_nonogram(rows: &[Vec<usize>], cols: &[Vec<usize>])
for x in 0..w {
sys.add_constraint(Nonogram::new(
&vars.iter().map(|row| row[x]).collect(),
&cols[x]));
&cols[x],
));
}
(sys, vars)
@ -270,24 +275,22 @@ fn nonogram_wikipedia() {
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0,0,0,0,0, 0,1,1,0,1, 0,0,0,0,0, 0,0,0,0,0 ] ];
[0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
let (mut sys, vars) = make_nonogram(&puzzle_rows, &puzzle_cols);
let dict = sys.solve_unique().expect("solution");

View File

@ -4,8 +4,8 @@
extern crate puzzle_solver;
use std::rc::Rc;
use puzzle_solver::*;
use std::rc::Rc;
struct NoDiagonal {
vars: Vec<VarToken>,
@ -16,9 +16,12 @@ impl Constraint for NoDiagonal {
Box::new(self.vars.iter())
}
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val)
-> PsResult<()> {
let y1 = self.vars.iter().position(|&v| v == var).expect("unreachable");
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val) -> 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;
@ -31,8 +34,7 @@ impl Constraint for NoDiagonal {
Ok(())
}
fn substitute(&self, _from: VarToken, _to: VarToken)
-> PsResult<Rc<dyn Constraint>> {
fn substitute(&self, _from: VarToken, _to: VarToken) -> PsResult<Rc<dyn Constraint>> {
unimplemented!();
}
}

View File

@ -28,15 +28,18 @@ fn make_sudoku(sys: &mut Puzzle) -> SudokuVars {
for block in 0..SIZE {
let x0 = SQRT_SIZE * (block % SQRT_SIZE);
let y0 = SQRT_SIZE * (block / SQRT_SIZE);
sys.all_different((0..SIZE).map(|n|
&vars[y0 + (n / SQRT_SIZE)][x0 + (n % SQRT_SIZE)]));
sys.all_different((0..SIZE).map(|n| &vars[y0 + (n / SQRT_SIZE)][x0 + (n % SQRT_SIZE)]));
}
vars
}
fn make_samurai_sudoku(board: &Board) -> (Puzzle, SamuraiVars) {
let set = |sys: &mut Puzzle, var, val| if val != 0 { sys.set_value(var, val) };
let set = |sys: &mut Puzzle, var, val| {
if val != 0 {
sys.set_value(var, val)
}
};
let mut sys = Puzzle::new();
let tl = make_sudoku(&mut sys);
@ -47,10 +50,22 @@ fn make_samurai_sudoku(board: &Board) -> (Puzzle, SamuraiVars) {
for y in 0..SQRT_SIZE {
for x in 0..SQRT_SIZE {
sys.unify(mid[0 * SQRT_SIZE + y][0 * SQRT_SIZE + x], tl[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x]);
sys.unify(mid[0 * SQRT_SIZE + y][2 * SQRT_SIZE + x], tr[2 * SQRT_SIZE + y][0 * SQRT_SIZE + x]);
sys.unify(mid[2 * SQRT_SIZE + y][0 * SQRT_SIZE + x], bl[0 * SQRT_SIZE + y][2 * SQRT_SIZE + x]);
sys.unify(mid[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x], br[0 * SQRT_SIZE + y][0 * SQRT_SIZE + x]);
sys.unify(
mid[0 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
tl[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
);
sys.unify(
mid[0 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
tr[2 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
);
sys.unify(
mid[2 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
bl[0 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
);
sys.unify(
mid[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
br[0 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
);
}
}
@ -59,8 +74,16 @@ fn make_samurai_sudoku(board: &Board) -> (Puzzle, SamuraiVars) {
set(&mut sys, tl[y][x], board[y][x]);
set(&mut sys, tr[y][x], board[y][SIZE + SQRT_SIZE + x]);
set(&mut sys, bl[y][x], board[SIZE + SQRT_SIZE + y][x]);
set(&mut sys, br[y][x], board[SIZE + SQRT_SIZE + y][SIZE + SQRT_SIZE + x]);
set(&mut sys, mid[y][x], board[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x]);
set(
&mut sys,
br[y][x],
board[SIZE + SQRT_SIZE + y][SIZE + SQRT_SIZE + x],
);
set(
&mut sys,
mid[y][x],
board[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
);
}
}
@ -70,7 +93,11 @@ fn make_samurai_sudoku(board: &Board) -> (Puzzle, SamuraiVars) {
fn print_samurai_sudoku(dict: &Solution, vars: &SamuraiVars) {
let &(ref tl, ref tr, ref bl, ref br, ref mid) = vars;
let pr3 = |a: &[VarToken], j| print!(" {}{}{}", dict[a[j]], dict[a[j + 1]], dict[a[j + 2]]);
let pr9 = |a| { pr3(a, 0); pr3(a, 3); pr3(a, 6); };
let pr9 = |a| {
pr3(a, 0);
pr3(a, 3);
pr3(a, 6);
};
let gap = || print!(" ");
for i in 0..SIZE {
@ -110,8 +137,14 @@ fn verify_samurai_sudoku(dict: &Solution, vars: &SamuraiVars, expected: &Board)
assert_eq!(dict[tl[i][j]], expected[i][j]);
assert_eq!(dict[tr[i][j]], expected[i][SIZE + SQRT_SIZE + j]);
assert_eq!(dict[bl[i][j]], expected[SIZE + SQRT_SIZE + i][j]);
assert_eq!(dict[br[i][j]], expected[SIZE + SQRT_SIZE + i][SIZE + SQRT_SIZE + j]);
assert_eq!(dict[mid[i][j]], expected[2 * SQRT_SIZE + i][2 * SQRT_SIZE + j]);
assert_eq!(
dict[br[i][j]],
expected[SIZE + SQRT_SIZE + i][SIZE + SQRT_SIZE + j]
);
assert_eq!(
dict[mid[i][j]],
expected[2 * SQRT_SIZE + i][2 * SQRT_SIZE + j]
);
}
}
}
@ -119,62 +152,136 @@ fn verify_samurai_sudoku(dict: &Solution, vars: &SamuraiVars, expected: &Board)
#[test]
fn samuraisudoku_easy() {
let puzzle = [
[ 0,0,3, 0,0,0, 2,0,0, X,X,X, 0,0,6, 0,0,0, 2,0,0 ],
[ 0,2,0, 4,0,8, 0,3,0, X,X,X, 0,3,0, 4,0,2, 0,8,0 ],
[ 8,0,0, 0,9,0, 0,0,4, X,X,X, 8,0,0, 0,1,0, 0,0,4 ],
[ 0,5,0, 6,0,1, 0,2,0, X,X,X, 0,2,0, 1,0,7, 0,9,0 ],
[ 0,0,8, 0,0,0, 6,0,0, X,X,X, 0,0,9, 0,0,0, 8,0,0 ],
[ 0,7,0, 8,0,4, 0,1,0, X,X,X, 0,8,0, 5,0,9, 0,4,0 ],
[ 1,0,0, 0,7,0, 0,0,0, 0,0,0, 0,0,0, 0,7,0, 0,0,5 ],
[ 0,4,0, 1,0,2, 0,0,0, 0,0,0, 0,0,0, 2,0,8, 0,7,0 ],
[ 0,0,9, 0,0,0, 0,0,0, 0,6,0, 0,0,0, 0,0,0, 1,0,0 ],
[ X,X,X, X,X,X, 0,0,0, 5,0,1, 0,0,0, X,X,X, X,X,X ],
[ X,X,X, X,X,X, 0,0,9, 0,0,0, 6,0,0, X,X,X, X,X,X ],
[ X,X,X, X,X,X, 0,0,0, 3,0,6, 0,0,0, X,X,X, X,X,X ],
[ 0,0,8, 0,0,0, 0,0,0, 0,7,0, 0,0,0, 0,0,0, 4,0,0 ],
[ 0,4,0, 5,0,1, 0,0,0, 0,0,0, 0,0,0, 9,0,5, 0,7,0 ],
[ 6,0,0, 0,2,0, 0,0,0, 0,0,0, 0,0,0, 0,6,0, 0,0,9 ],
[ 0,9,0, 1,0,3, 0,7,0, X,X,X, 0,7,0, 5,0,1, 0,9,0 ],
[ 0,0,5, 0,0,0, 1,0,0, X,X,X, 0,0,3, 0,0,0, 6,0,0 ],
[ 0,1,0, 6,0,8, 0,9,0, X,X,X, 0,2,0, 8,0,6, 0,1,0 ],
[ 5,0,0, 0,7,0, 0,0,6, X,X,X, 7,0,0, 0,2,0, 0,0,5 ],
[ 0,2,0, 3,0,5, 0,1,0, X,X,X, 0,9,0, 6,0,4, 0,3,0 ],
[ 0,0,6, 0,0,0, 2,0,0, X,X,X, 0,0,4, 0,0,0, 1,0,0 ] ];
[
0, 0, 3, 0, 0, 0, 2, 0, 0, X, X, X, 0, 0, 6, 0, 0, 0, 2, 0, 0,
],
[
0, 2, 0, 4, 0, 8, 0, 3, 0, X, X, X, 0, 3, 0, 4, 0, 2, 0, 8, 0,
],
[
8, 0, 0, 0, 9, 0, 0, 0, 4, X, X, X, 8, 0, 0, 0, 1, 0, 0, 0, 4,
],
[
0, 5, 0, 6, 0, 1, 0, 2, 0, X, X, X, 0, 2, 0, 1, 0, 7, 0, 9, 0,
],
[
0, 0, 8, 0, 0, 0, 6, 0, 0, X, X, X, 0, 0, 9, 0, 0, 0, 8, 0, 0,
],
[
0, 7, 0, 8, 0, 4, 0, 1, 0, X, X, X, 0, 8, 0, 5, 0, 9, 0, 4, 0,
],
[
1, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 5,
],
[
0, 4, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 8, 0, 7, 0,
],
[
0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
],
[
X, X, X, X, X, X, 0, 0, 0, 5, 0, 1, 0, 0, 0, X, X, X, X, X, X,
],
[
X, X, X, X, X, X, 0, 0, 9, 0, 0, 0, 6, 0, 0, X, X, X, X, X, X,
],
[
X, X, X, X, X, X, 0, 0, 0, 3, 0, 6, 0, 0, 0, X, X, X, X, X, X,
],
[
0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0,
],
[
0, 4, 0, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 5, 0, 7, 0,
],
[
6, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 9,
],
[
0, 9, 0, 1, 0, 3, 0, 7, 0, X, X, X, 0, 7, 0, 5, 0, 1, 0, 9, 0,
],
[
0, 0, 5, 0, 0, 0, 1, 0, 0, X, X, X, 0, 0, 3, 0, 0, 0, 6, 0, 0,
],
[
0, 1, 0, 6, 0, 8, 0, 9, 0, X, X, X, 0, 2, 0, 8, 0, 6, 0, 1, 0,
],
[
5, 0, 0, 0, 7, 0, 0, 0, 6, X, X, X, 7, 0, 0, 0, 2, 0, 0, 0, 5,
],
[
0, 2, 0, 3, 0, 5, 0, 1, 0, X, X, X, 0, 9, 0, 6, 0, 4, 0, 3, 0,
],
[
0, 0, 6, 0, 0, 0, 2, 0, 0, X, X, X, 0, 0, 4, 0, 0, 0, 1, 0, 0,
],
];
let expected = [
[ 4,9,3, 7,1,5, 2,6,8, X,X,X, 9,4,6, 8,3,5, 2,1,7 ],
[ 5,2,7, 4,6,8, 9,3,1, X,X,X, 1,3,7, 4,9,2, 5,8,6 ],
[ 8,6,1, 2,9,3, 5,7,4, X,X,X, 8,5,2, 7,1,6, 9,3,4 ],
[ 9,5,4, 6,3,1, 8,2,7, X,X,X, 5,2,4, 1,8,7, 6,9,3 ],
[ 3,1,8, 9,2,7, 6,4,5, X,X,X, 7,1,9, 6,4,3, 8,5,2 ],
[ 2,7,6, 8,5,4, 3,1,9, X,X,X, 6,8,3, 5,2,9, 7,4,1 ],
[ 1,8,2, 3,7,9, 4,5,6, 7,1,3, 2,9,8, 3,7,1, 4,6,5 ],
[ 6,4,5, 1,8,2, 7,9,3, 8,5,2, 4,6,1, 2,5,8, 3,7,9 ],
[ 7,3,9, 5,4,6, 1,8,2, 9,6,4, 3,7,5, 9,6,4, 1,2,8 ],
[ X,X,X, X,X,X, 6,4,8, 5,9,1, 7,2,3, X,X,X, X,X,X ],
[ X,X,X, X,X,X, 3,1,9, 2,8,7, 6,5,4, X,X,X, X,X,X ],
[ X,X,X, X,X,X, 2,7,5, 3,4,6, 1,8,9, X,X,X, X,X,X ],
[ 1,7,8, 9,3,6, 5,2,4, 1,7,8, 9,3,6, 2,8,7, 4,5,1 ],
[ 2,4,3, 5,8,1, 9,6,7, 4,3,5, 8,1,2, 9,4,5, 3,7,6 ],
[ 6,5,9, 4,2,7, 8,3,1, 6,2,9, 5,4,7, 1,6,3, 8,2,9 ],
[ 8,9,2, 1,4,3, 6,7,5, X,X,X, 6,7,8, 5,3,1, 2,9,4 ],
[ 4,6,5, 7,9,2, 1,8,3, X,X,X, 1,5,3, 4,9,2, 6,8,7 ],
[ 3,1,7, 6,5,8, 4,9,2, X,X,X, 4,2,9, 8,7,6, 5,1,3 ],
[ 5,8,1, 2,7,9, 3,4,6, X,X,X, 7,6,1, 3,2,8, 9,4,5 ],
[ 9,2,4, 3,6,5, 7,1,8, X,X,X, 2,9,5, 6,1,4, 7,3,8 ],
[ 7,3,6, 8,1,4, 2,5,9, X,X,X, 3,8,4, 7,5,9, 1,6,2 ] ];
[
4, 9, 3, 7, 1, 5, 2, 6, 8, X, X, X, 9, 4, 6, 8, 3, 5, 2, 1, 7,
],
[
5, 2, 7, 4, 6, 8, 9, 3, 1, X, X, X, 1, 3, 7, 4, 9, 2, 5, 8, 6,
],
[
8, 6, 1, 2, 9, 3, 5, 7, 4, X, X, X, 8, 5, 2, 7, 1, 6, 9, 3, 4,
],
[
9, 5, 4, 6, 3, 1, 8, 2, 7, X, X, X, 5, 2, 4, 1, 8, 7, 6, 9, 3,
],
[
3, 1, 8, 9, 2, 7, 6, 4, 5, X, X, X, 7, 1, 9, 6, 4, 3, 8, 5, 2,
],
[
2, 7, 6, 8, 5, 4, 3, 1, 9, X, X, X, 6, 8, 3, 5, 2, 9, 7, 4, 1,
],
[
1, 8, 2, 3, 7, 9, 4, 5, 6, 7, 1, 3, 2, 9, 8, 3, 7, 1, 4, 6, 5,
],
[
6, 4, 5, 1, 8, 2, 7, 9, 3, 8, 5, 2, 4, 6, 1, 2, 5, 8, 3, 7, 9,
],
[
7, 3, 9, 5, 4, 6, 1, 8, 2, 9, 6, 4, 3, 7, 5, 9, 6, 4, 1, 2, 8,
],
[
X, X, X, X, X, X, 6, 4, 8, 5, 9, 1, 7, 2, 3, X, X, X, X, X, X,
],
[
X, X, X, X, X, X, 3, 1, 9, 2, 8, 7, 6, 5, 4, X, X, X, X, X, X,
],
[
X, X, X, X, X, X, 2, 7, 5, 3, 4, 6, 1, 8, 9, X, X, X, X, X, X,
],
[
1, 7, 8, 9, 3, 6, 5, 2, 4, 1, 7, 8, 9, 3, 6, 2, 8, 7, 4, 5, 1,
],
[
2, 4, 3, 5, 8, 1, 9, 6, 7, 4, 3, 5, 8, 1, 2, 9, 4, 5, 3, 7, 6,
],
[
6, 5, 9, 4, 2, 7, 8, 3, 1, 6, 2, 9, 5, 4, 7, 1, 6, 3, 8, 2, 9,
],
[
8, 9, 2, 1, 4, 3, 6, 7, 5, X, X, X, 6, 7, 8, 5, 3, 1, 2, 9, 4,
],
[
4, 6, 5, 7, 9, 2, 1, 8, 3, X, X, X, 1, 5, 3, 4, 9, 2, 6, 8, 7,
],
[
3, 1, 7, 6, 5, 8, 4, 9, 2, X, X, X, 4, 2, 9, 8, 7, 6, 5, 1, 3,
],
[
5, 8, 1, 2, 7, 9, 3, 4, 6, X, X, X, 7, 6, 1, 3, 2, 8, 9, 4, 5,
],
[
9, 2, 4, 3, 6, 5, 7, 1, 8, X, X, X, 2, 9, 5, 6, 1, 4, 7, 3, 8,
],
[
7, 3, 6, 8, 1, 4, 2, 5, 9, X, X, X, 3, 8, 4, 7, 5, 9, 1, 6, 2,
],
];
let (mut sys, vars) = make_samurai_sudoku(&puzzle);
let dict = sys.solve_any().expect("solution");

View File

@ -32,7 +32,10 @@ fn print_send_more_money(dict: &Solution, vars: &Vec<VarToken>) {
println!(" {} {} {} {}", dict[s], dict[e], dict[n], dict[d]);
println!(" + {} {} {} {}", dict[m], dict[o], dict[r], dict[e]);
println!("----------");
println!(" {} {} {} {} {}", dict[m], dict[o], dict[n], dict[e], dict[y]);
println!(
" {} {} {} {} {}",
dict[m], dict[o], dict[n], dict[e], dict[y]
);
}
fn verify_send_more_money(dict: &Solution, vars: &Vec<VarToken>) {

View File

@ -25,8 +25,7 @@ fn make_sudoku(board: &Board) -> (Puzzle, Vec<Vec<VarToken>>) {
for block in 0..SIZE {
let x0 = SQRT_SIZE * (block % SQRT_SIZE);
let y0 = SQRT_SIZE * (block / SQRT_SIZE);
sys.all_different((0..SIZE).map(|n|
&vars[y0 + (n / SQRT_SIZE)][x0 + (n % SQRT_SIZE)]));
sys.all_different((0..SIZE).map(|n| &vars[y0 + (n / SQRT_SIZE)][x0 + (n % SQRT_SIZE)]));
}
for y in 0..SIZE {
@ -48,9 +47,11 @@ fn print_sudoku(dict: &Solution, vars: &Vec<Vec<VarToken>>) {
}
for x in 0..SIZE {
print!("{}{}",
print!(
"{}{}",
if x % SQRT_SIZE == 0 { " " } else { "" },
dict[vars[y][x]]);
dict[vars[y][x]]
);
}
println!();
}
@ -70,27 +71,25 @@ fn sudoku_hardest() {
[8, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 6, 0, 0, 0, 0, 0],
[0, 7, 0, 0, 9, 0, 2, 0, 0],
[0, 5, 0, 0, 0, 7, 0, 0, 0],
[0, 0, 0, 0, 4, 5, 7, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 3, 0],
[0, 0, 1, 0, 0, 0, 0, 6, 8],
[0, 0, 8, 5, 0, 0, 0, 1, 0],
[ 0,9,0, 0,0,0, 4,0,0 ] ];
[0, 9, 0, 0, 0, 0, 4, 0, 0],
];
let expected = [
[8, 1, 2, 7, 5, 3, 6, 4, 9],
[9, 4, 3, 6, 8, 2, 1, 7, 5],
[6, 7, 5, 4, 9, 1, 2, 8, 3],
[1, 5, 4, 2, 3, 7, 8, 9, 6],
[3, 6, 9, 8, 4, 5, 7, 2, 1],
[2, 8, 7, 1, 6, 9, 5, 3, 4],
[5, 2, 1, 9, 7, 4, 3, 6, 8],
[4, 3, 8, 5, 2, 6, 9, 1, 7],
[ 7,9,6, 3,1,8, 4,5,2 ] ];
[7, 9, 6, 3, 1, 8, 4, 5, 2],
];
let (mut sys, vars) = make_sudoku(&puzzle);
let solution = sys.solve_any().expect("solution");
@ -105,27 +104,25 @@ fn sudoku_wikipedia() {
[5, 3, 0, 0, 7, 0, 0, 0, 0],
[6, 0, 0, 1, 9, 5, 0, 0, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[ 0,0,0, 0,8,0, 0,7,9 ] ];
[0, 0, 0, 0, 8, 0, 0, 7, 9],
];
let expected = [
[5, 3, 4, 6, 7, 8, 9, 1, 2],
[6, 7, 2, 1, 9, 5, 3, 4, 8],
[1, 9, 8, 3, 4, 2, 5, 6, 7],
[8, 5, 9, 7, 6, 1, 4, 2, 3],
[4, 2, 6, 8, 5, 3, 7, 9, 1],
[7, 1, 3, 9, 2, 4, 8, 5, 6],
[9, 6, 1, 5, 3, 7, 2, 8, 4],
[2, 8, 7, 4, 1, 9, 6, 3, 5],
[ 3,4,5, 2,8,6, 1,7,9 ] ];
[3, 4, 5, 2, 8, 6, 1, 7, 9],
];
let (mut sys, vars) = make_sudoku(&puzzle);
let solution = sys.solve_any().expect("solution");

View File

@ -10,8 +10,7 @@ use puzzle_solver::{Puzzle,Solution,Val,VarToken};
const SIZE: usize = 3;
type Board = [[Val; SIZE]; SIZE];
fn make_sujiko(board: &Board, tl: Val, tr: Val, bl: Val, br: Val)
-> (Puzzle, Vec<Vec<VarToken>>) {
fn make_sujiko(board: &Board, tl: Val, tr: Val, bl: Val, br: Val) -> (Puzzle, Vec<Vec<VarToken>>) {
let mut sys = Puzzle::new();
let vars = sys.new_vars_with_candidates_2d(3, 3, &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
@ -22,8 +21,10 @@ fn make_sujiko(board: &Board, tl: Val, tr: Val, bl: Val, br: Val)
sys.equals(bl, vars[1][0] + vars[1][1] + vars[2][0] + vars[2][1]);
sys.equals(br, vars[1][1] + vars[1][2] + vars[2][1] + vars[2][2]);
sys.equals(tl + tr + bl + br - (1..(9 + 1)).sum::<Val>(),
vars[0][1] + vars[1][0] + 3 * vars[1][1] + vars[1][2] + vars[2][1]);
sys.equals(
tl + tr + bl + br - (1..(9 + 1)).sum::<Val>(),
vars[0][1] + vars[1][0] + 3 * vars[1][1] + vars[1][2] + vars[2][1],
);
for y in 0..SIZE {
for x in 0..SIZE {

View File

@ -4,9 +4,9 @@
extern crate puzzle_solver;
use puzzle_solver::*;
use std::iter;
use std::rc::Rc;
use puzzle_solver::*;
const X: Val = -1;
@ -23,8 +23,7 @@ impl Constraint for BinaryRepr {
Box::new(iter::once(&self.value).chain(&self.bits))
}
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val)
-> PsResult<()> {
fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val) -> PsResult<()> {
if var == self.value {
let mut val = val;
for &var in self.bits.iter() {
@ -33,7 +32,8 @@ impl Constraint for BinaryRepr {
}
} else if let Some(bitpos) = self.bits.iter().position(|&v| v == var) {
let bit = 1 << bitpos;
let discard = search.get_unassigned(self.value)
let discard = search
.get_unassigned(self.value)
.filter(|c| c & bit != val * bit)
.collect::<Vec<_>>();
@ -45,8 +45,7 @@ impl Constraint for BinaryRepr {
Ok(())
}
fn substitute(&self, _from: VarToken, _to: VarToken)
-> PsResult<Rc<dyn Constraint>> {
fn substitute(&self, _from: VarToken, _to: VarToken) -> PsResult<Rc<dyn Constraint>> {
unimplemented!();
}
}
@ -89,7 +88,10 @@ fn make_takuzu(puzzle: &Vec<Vec<Val>>) -> (Puzzle, Vec<Vec<VarToken>>) {
for y in 0..height {
let total = (height as Val) / 2;
sys.equals(total, vars[y].iter().fold(LinExpr::from(0), |sum, &x| sum + x));
sys.equals(
total,
vars[y].iter().fold(LinExpr::from(0), |sum, &x| sum + x),
);
sys.add_constraint(BinaryRepr {
value: row_values[y],
bits: vars[y].clone(),
@ -98,7 +100,10 @@ fn make_takuzu(puzzle: &Vec<Vec<Val>>) -> (Puzzle, Vec<Vec<VarToken>>) {
for x in 0..width {
let total = (width as Val) / 2;
sys.equals(total, vars.iter().fold(LinExpr::from(0), |sum, row| sum + row[x]));
sys.equals(
total,
vars.iter().fold(LinExpr::from(0), |sum, row| sum + row[x]),
);
sys.add_constraint(BinaryRepr {
value: col_values[x],
bits: (0..height).map(|y| vars[y][x]).collect(),
@ -116,7 +121,10 @@ fn make_takuzu(puzzle: &Vec<Vec<Val>>) -> (Puzzle, Vec<Vec<VarToken>>) {
for x in 0..width {
for y in 0..(height - 2) {
let disjunction = sys.new_var_with_candidates(&[1, 2]);
sys.equals(vars[y + 0][x] + vars[y + 1][x] + vars[y + 2][x], disjunction);
sys.equals(
vars[y + 0][x] + vars[y + 1][x] + vars[y + 2][x],
disjunction,
);
}
}
@ -159,7 +167,8 @@ fn takuzu_grid1() {
vec![X, X, 0, X, X, X],
vec![1, 1, X, X, 1, 0],
vec![X, X, X, X, 0, X],
vec![ X,X,X,X,X,X ] ];
vec![X, X, X, X, X, X],
];
let (mut sys, vars) = make_takuzu(&puzzle);
let solutions = sys.solve_all();
@ -183,7 +192,8 @@ fn takuzu_grid2() {
vec![X, X, 0, 0, X, X, 0, X, 0, X, X, 0],
vec![X, X, X, X, X, 1, X, X, X, X, 1, X],
vec![1, 0, X, 0, X, X, X, X, X, X, X, X],
vec![ X,X,1,X,X,X,X,1,X,X,0,0 ] ];
vec![X, X, 1, X, X, X, X, 1, X, X, 0, 0],
];
let expected = [
0b_010101101001,
@ -197,7 +207,8 @@ fn takuzu_grid2() {
0b_110010010110,
0b_010101101010,
0b_101010010101,
0b_101011010100 ];
0b_101011010100,
];
let (mut sys, vars) = make_takuzu(&puzzle);
let dict = sys.solve_unique().expect("solution");
@ -220,7 +231,8 @@ fn takuzu_grid3() {
vec![X, X, X, X, X, X, 1, 0, 1, X, 0, X],
vec![X, 1, X, X, 0, X, X, X, X, 0, 0, X],
vec![X, X, X, 1, X, X, X, 0, X, X, X, X],
vec![ X,X,X,X,X,1,1,X,X,1,X,X ] ];
vec![X, X, X, X, X, 1, 1, X, X, 1, X, X],
];
let expected = [
0b_101010011001,
@ -234,7 +246,8 @@ fn takuzu_grid3() {
0b_010010101101,
0b_011001011001,
0b_100110100110,
0b_010101100110 ];
0b_010101100110,
];
let (mut sys, vars) = make_takuzu(&puzzle);
let dict = sys.solve_unique().expect("solution");
@ -257,7 +270,8 @@ fn takuzu_grid4() {
vec![X, X, 0, 0, X, X, 0, X, 0, X, X, 0],
vec![X, X, X, X, X, 1, X, X, X, X, 1, X],
vec![X, X, X, 0, X, X, X, X, X, X, X, X],
vec![ X,X,1,X,X,X,X,1,X,X,0,0 ] ];
vec![X, X, 1, X, X, X, X, 1, X, X, 0, 0],
];
let (mut sys, vars) = make_takuzu(&puzzle);
let dict = &sys.solve_any().expect("solution");

View File

@ -32,8 +32,12 @@ fn xkcd_knapsack() {
vars.push(var)
}
sys.equals(total, vars.iter().zip(menu.iter()).fold(LinExpr::from(0),
|sum, (&var, &(cost, _))| sum + var * cost));
sys.equals(
total,
vars.iter()
.zip(menu.iter())
.fold(LinExpr::from(0), |sum, (&var, &(cost, _))| sum + var * cost),
);
let solutions = sys.solve_all();
assert_eq!(solutions.len(), 2);

View File

@ -10,23 +10,57 @@ extern crate puzzle_solver;
use puzzle_solver::Puzzle;
#[derive(Clone, Copy, Debug)]
enum Nat { Dane, Englishman, German, Norwegian, Swede }
enum Nat {
Dane,
Englishman,
German,
Norwegian,
Swede,
}
#[derive(Clone, Copy, Debug)]
enum Col { Blue, Green, Red, White, Yellow }
enum Col {
Blue,
Green,
Red,
White,
Yellow,
}
#[derive(Clone, Copy, Debug)]
enum Dri { Beer, Coffee, Milk, Tea, Water }
enum Dri {
Beer,
Coffee,
Milk,
Tea,
Water,
}
#[derive(Clone, Copy, Debug)]
enum Smo { Blend, BlueMaster, Dunhill, PallMall, Prince }
enum Smo {
Blend,
BlueMaster,
Dunhill,
PallMall,
Prince,
}
#[derive(Clone, Copy, Debug)]
enum Pet { Bird, Cat, Dog, Fish, Horse }
enum Pet {
Bird,
Cat,
Dog,
Fish,
Horse,
}
#[test]
fn zebra() {
use crate::Nat::*; use crate::Col::*; use crate::Dri::*; use crate::Smo::*; use crate::Pet::*;
use crate::Col::*;
use crate::Dri::*;
use crate::Nat::*;
use crate::Pet::*;
use crate::Smo::*;
// #1: There are five houses.
let mut sys = Puzzle::new();
@ -104,7 +138,8 @@ fn zebra() {
(Dane, Blue, Tea, Blend, Horse),
(Englishman, Red, Milk, PallMall, Bird),
(German, Green, Coffee, Prince, Fish),
(Swede, White, Beer, BlueMaster, Dog) ];
(Swede, White, Beer, BlueMaster, Dog),
];
for &(n, c, d, s, p) in expected.iter() {
assert_eq!(dict[nat(n)], dict[col(c)]);