Apply puzzle constraints.
For simplicity, we currently wake every constraint whenever there is a change. A constraint's on_assigned function must take this into account.
This commit is contained in:
parent
d199b65a22
commit
460e91e16b
144
src/puzzle.rs
144
src/puzzle.rs
@ -40,6 +40,10 @@ pub struct Puzzle {
|
|||||||
pub struct PuzzleSearch<'a> {
|
pub struct PuzzleSearch<'a> {
|
||||||
puzzle: &'a Puzzle,
|
puzzle: &'a Puzzle,
|
||||||
vars: Vec<VarState>,
|
vars: Vec<VarState>,
|
||||||
|
|
||||||
|
// A variable to be marked by constraints when updating the list
|
||||||
|
// of candidates.
|
||||||
|
modified: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------*/
|
/*--------------------------------------------------------------*/
|
||||||
@ -290,9 +294,12 @@ impl Puzzle {
|
|||||||
/// assert!(solution.is_some());
|
/// assert!(solution.is_some());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn solve_any(&self) -> Option<Solution> {
|
pub fn solve_any(&self) -> Option<Solution> {
|
||||||
let mut search = PuzzleSearch::new(self);
|
|
||||||
let mut solutions = Vec::with_capacity(1);
|
let mut solutions = Vec::with_capacity(1);
|
||||||
search.solve(1, &mut solutions);
|
if self.num_vars > 0 {
|
||||||
|
let mut search = PuzzleSearch::new(self);
|
||||||
|
search.solve(1, &mut solutions);
|
||||||
|
}
|
||||||
|
|
||||||
solutions.pop()
|
solutions.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,11 +317,13 @@ impl Puzzle {
|
|||||||
/// assert!(solution.is_none());
|
/// assert!(solution.is_none());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn solve_unique(&self) -> Option<Solution> {
|
pub fn solve_unique(&self) -> Option<Solution> {
|
||||||
let mut search = PuzzleSearch::new(self);
|
if self.num_vars > 0 {
|
||||||
let mut solutions = Vec::with_capacity(2);
|
let mut search = PuzzleSearch::new(self);
|
||||||
search.solve(2, &mut solutions);
|
let mut solutions = Vec::with_capacity(2);
|
||||||
if solutions.len() == 1 {
|
search.solve(2, &mut solutions);
|
||||||
return solutions.pop();
|
if solutions.len() == 1 {
|
||||||
|
return solutions.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -333,11 +342,31 @@ impl Puzzle {
|
|||||||
/// assert_eq!(solutions.len(), 4);
|
/// assert_eq!(solutions.len(), 4);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn solve_all(&self) -> Vec<Solution> {
|
pub fn solve_all(&self) -> Vec<Solution> {
|
||||||
let mut search = PuzzleSearch::new(self);
|
|
||||||
let mut solutions = Vec::new();
|
let mut solutions = Vec::new();
|
||||||
search.solve(::std::usize::MAX, &mut solutions);
|
if self.num_vars > 0 {
|
||||||
|
let mut search = PuzzleSearch::new(self);
|
||||||
|
search.solve(::std::usize::MAX, &mut solutions);
|
||||||
|
}
|
||||||
|
|
||||||
solutions
|
solutions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take any obvious non-choices, using the constraints to
|
||||||
|
/// eliminate candidates. Stops when it must start guessing.
|
||||||
|
/// Primarily for testing.
|
||||||
|
///
|
||||||
|
/// Returns the intermediate puzzle search state, or None if a
|
||||||
|
/// contradiction was found.
|
||||||
|
pub fn step(&self) -> Option<PuzzleSearch> {
|
||||||
|
if self.num_vars > 0 {
|
||||||
|
let mut search = PuzzleSearch::new(self);
|
||||||
|
if search.constrain() {
|
||||||
|
return Some(search);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------*/
|
/*--------------------------------------------------------------*/
|
||||||
@ -353,6 +382,7 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
PuzzleSearch {
|
PuzzleSearch {
|
||||||
puzzle: puzzle,
|
puzzle: puzzle,
|
||||||
vars: vars,
|
vars: vars,
|
||||||
|
modified: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,12 +416,14 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
&mut Candidates::Value(v) => {
|
&mut Candidates::Value(v) => {
|
||||||
if v == val {
|
if v == val {
|
||||||
*cs = Candidates::None;
|
*cs = Candidates::None;
|
||||||
|
self.modified = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
&mut Candidates::Set(ref mut rc) => {
|
&mut Candidates::Set(ref mut rc) => {
|
||||||
if rc.contains(&val) {
|
if rc.contains(&val) {
|
||||||
let mut set = Rc::make_mut(rc);
|
let mut set = Rc::make_mut(rc);
|
||||||
set.remove(&val);
|
set.remove(&val);
|
||||||
|
self.modified = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -400,6 +432,10 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
|
|
||||||
/// 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() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let next_unassigned = self.vars.iter().enumerate().min_by_key(
|
let next_unassigned = self.vars.iter().enumerate().min_by_key(
|
||||||
|&(_, vs)| match vs {
|
|&(_, vs)| match vs {
|
||||||
&VarState::Assigned(_) => ::std::usize::MAX,
|
&VarState::Assigned(_) => ::std::usize::MAX,
|
||||||
@ -414,7 +450,9 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
|
|
||||||
for val in cs.iter() {
|
for val in cs.iter() {
|
||||||
let mut new = self.clone();
|
let mut new = self.clone();
|
||||||
new.vars[idx] = VarState::Assigned(val);
|
if !new.assign(idx, val) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
new.solve(count, solutions);
|
new.solve(count, solutions);
|
||||||
if solutions.len() >= count {
|
if solutions.len() >= count {
|
||||||
@ -434,6 +472,78 @@ impl<'a> PuzzleSearch<'a> {
|
|||||||
solutions.push(Solution{ vars: vars });
|
solutions.push(Solution{ vars: vars });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
let var = VarToken(idx);
|
||||||
|
self.vars[idx] = VarState::Assigned(val);
|
||||||
|
self.modified = true;
|
||||||
|
|
||||||
|
for constraint in self.puzzle.constraints.iter() {
|
||||||
|
if !constraint.on_assigned(self, var, val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
let mut repeat = true;
|
||||||
|
|
||||||
|
while repeat || self.modified {
|
||||||
|
repeat = false;
|
||||||
|
|
||||||
|
// "Gimme" phase:
|
||||||
|
// - abort if any variables with 0 candidates,
|
||||||
|
// - assign variables with only 1 candidate.
|
||||||
|
// - repeat until no more gimmes found.
|
||||||
|
let cycle = self.vars.len();
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut last_gimme = cycle - 1;
|
||||||
|
loop {
|
||||||
|
let gimme = match &self.vars[idx] {
|
||||||
|
&VarState::Assigned(_) => None,
|
||||||
|
&VarState::Unassigned(ref cs) => match cs.len() {
|
||||||
|
0 => return false,
|
||||||
|
1 => cs.iter().next(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(val) = gimme {
|
||||||
|
if !self.assign(idx, val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
repeat = true;
|
||||||
|
last_gimme = idx;
|
||||||
|
} else if idx == last_gimme {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx = if idx + 1 >= cycle { 0 } else { idx + 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply constraints.
|
||||||
|
if repeat || self.modified {
|
||||||
|
self.modified = false;
|
||||||
|
|
||||||
|
for constraint in self.puzzle.constraints.iter() {
|
||||||
|
if !constraint.on_updated(self) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Index<VarToken> for PuzzleSearch<'a> {
|
impl<'a> Index<VarToken> for PuzzleSearch<'a> {
|
||||||
@ -452,3 +562,17 @@ impl<'a> Index<VarToken> for PuzzleSearch<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use ::Puzzle;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_vars() {
|
||||||
|
let sys = Puzzle::new();
|
||||||
|
sys.solve_any();
|
||||||
|
sys.solve_unique();
|
||||||
|
sys.solve_all();
|
||||||
|
sys.step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user