diff --git a/Cargo.toml b/Cargo.toml index 80917f2..3e9cefe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ path = "./bin/run.rs" bit-set = "0.4" num-rational = { version = "0.1", default-features = false } num-traits = "0.1" +ranges = "0.3.3" thiserror = "1.0.30" [badges] diff --git a/src/constraint/alldifferent.rs b/src/constraint/alldifferent.rs index 492acd4..c01546a 100644 --- a/src/constraint/alldifferent.rs +++ b/src/constraint/alldifferent.rs @@ -1,10 +1,11 @@ //! All different implementation. -use std::collections::HashMap; +use std::collections::{hash_map::Entry, HashMap}; use std::rc::Rc; use crate::{Constraint, Error, PsResult, PuzzleSearch, Val, VarToken}; +#[derive(Debug)] pub struct AllDifferent { vars: Vec, } @@ -32,7 +33,7 @@ impl AllDifferent { } impl Constraint for AllDifferent { - fn vars<'a>(&'a self) -> Box + 'a> { + fn vars(&self) -> Box + '_> { Box::new(self.vars.iter()) } @@ -54,10 +55,11 @@ impl Constraint for AllDifferent { for val in search.get_unassigned(var) { match all_candidates.entry(val) { - std::collections::hash_map::Entry::Occupied(mut e) => { + Entry::Occupied(mut e) => { e.insert(None); } - std::collections::hash_map::Entry::Vacant(e) => { + + Entry::Vacant(e) => { e.insert(Some(var)); } } diff --git a/src/constraint/equality.rs b/src/constraint/equality.rs index a08d660..557d5e1 100644 --- a/src/constraint/equality.rs +++ b/src/constraint/equality.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use crate::{Constraint, Error, LinExpr, PsResult, PuzzleSearch, Val, VarToken}; +#[derive(Debug)] pub struct Equality { // The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ... eqn: LinExpr, @@ -30,11 +31,11 @@ impl Equality { } impl Constraint for Equality { - fn vars<'a>(&'a self) -> Box + 'a> { + fn vars(&self) -> Box + '_> { Box::new(self.eqn.coef.keys()) } - fn on_assigned(&self, search: &mut PuzzleSearch, _: VarToken, _: Val) -> PsResult<()> { + fn on_assigned(&self, search: &mut PuzzleSearch, _var: VarToken, _val: Val) -> PsResult<()> { let mut sum = self.eqn.constant; let mut unassigned_var = None; @@ -101,23 +102,25 @@ impl Constraint for Equality { } let (min_val, max_val) = search.get_min_max(var)?; - 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(); + let (min_bnd, max_bnd) = if coef > Ratio::zero() { + ( + ((coef * Ratio::from_integer(max_val) - sum_max) / coef) + .ceil() + .to_integer(), + ((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(); - } + ( + ((coef * Ratio::from_integer(max_val) - sum_min) / coef) + .ceil() + .to_integer(), + ((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) = search.bound_candidate_range(var, min_bnd, max_bnd)?; diff --git a/src/constraint/mod.rs b/src/constraint/mod.rs index 74481f0..0e2ec81 100644 --- a/src/constraint/mod.rs +++ b/src/constraint/mod.rs @@ -5,14 +5,15 @@ //! cannot store additional information about the state (e.g. caches) //! in the constraint to reuse later. +use std::fmt::Debug; use std::rc::Rc; use crate::{PsResult, PuzzleSearch, Val, VarToken}; /// Constraint trait. -pub trait Constraint { +pub trait Constraint: Debug { /// An iterator over the variables that are involved in the constraint. - fn vars<'a>(&'a self) -> Box + 'a>; + fn vars(&self) -> Box + '_>; /// Applied after a variable has been assigned. fn on_assigned(&self, _search: &mut PuzzleSearch, _var: VarToken, _val: Val) -> PsResult<()> { diff --git a/src/constraint/unify.rs b/src/constraint/unify.rs index 67b0d23..2402ad3 100644 --- a/src/constraint/unify.rs +++ b/src/constraint/unify.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use crate::{Constraint, PsResult, PuzzleSearch, VarToken}; +#[derive(Debug)] pub struct Unify { var1: VarToken, var2: VarToken, @@ -25,15 +26,12 @@ impl Unify { /// puzzle_solver::constraint::Unify::new(m, carry[3]); /// ``` pub fn new(var1: VarToken, var2: VarToken) -> Self { - Unify { - var1, - var2, - } + Unify { var1, var2 } } } impl Constraint for Unify { - fn vars<'a>(&'a self) -> Box + 'a> { + fn vars(&self) -> Box + '_> { if self.var1 != self.var2 { Box::new(iter::once(&self.var1).chain(iter::once(&self.var2))) } else { @@ -52,10 +50,7 @@ impl Constraint for Unify { fn substitute(&self, from: VarToken, to: VarToken) -> PsResult> { 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, - var2, - })) + Ok(Rc::new(Unify { var1, var2 })) } } diff --git a/src/lib.rs b/src/lib.rs index 12c3c4a..34477ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,9 @@ mod error; mod linexpr; mod puzzle; +use core::fmt; use num_rational::Rational32; +use num_traits::Signed; use std::collections::HashMap; use std::ops; @@ -41,6 +43,34 @@ pub struct LinExpr { coef: HashMap, } +impl fmt::Display for LinExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.constant)?; + + for (tok, coef) in self.coef.iter() { + if coef.is_negative() { + if coef.abs() == Rational32::from_integer(1) { + write!(f, " - x{}", tok.0)?; + } else { + write!(f, " - {} * x{}", coef.abs(), tok.0)?; + } + } else if coef.abs() == Rational32::from_integer(1) { + write!(f, " + x{}", tok.0)?; + } else { + write!(f, " + {} * x{}", coef, tok.0)?; + } + } + + Ok(()) + } +} + +impl fmt::Debug for LinExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "LinExpr {{ {} }}", self) + } +} + /// A result during a puzzle solution search (Err = contradiction). pub type PsResult = Result; diff --git a/src/puzzle.rs b/src/puzzle.rs index d3b0852..f9e6529 100644 --- a/src/puzzle.rs +++ b/src/puzzle.rs @@ -1,24 +1,30 @@ //! The puzzle's state and rules. use bit_set::BitSet; +use ranges::GenericRange; use std::cell::Cell; use std::collections::BTreeSet; use std::fmt; use std::iter; use std::mem; use std::ops; +use std::ops::Bound; +use std::ops::RangeBounds; use std::rc::Rc; use crate::constraint; use crate::Error; use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken}; +use ranges::Ranges; + /// A collection of candidates. #[derive(Clone, Debug, Eq, PartialEq)] enum Candidates { None, // A variable with no candidates. Value(Val), // A variable set to its initial value. Set(Rc>), // A variable with a list of candidates. + Range(Ranges), // A variable with candidate ranges. } /// The state of a variable during the solution search. @@ -75,15 +81,38 @@ impl Candidates { Candidates::None => 0, Candidates::Value(_) => 1, Candidates::Set(ref rc) => rc.len(), + Candidates::Range(r) => { + let mut total = 0; + + for i in r.as_slice() { + let min = match i.start_bound() { + Bound::Included(val) => *val, + Bound::Excluded(val) => val + 1, + Bound::Unbounded => unreachable!(), + }; + + let max = match i.end_bound() { + Bound::Included(val) => *val, + Bound::Excluded(val) => val - 1, + Bound::Unbounded => unreachable!(), + }; + + total += max - min + 1; + } + total as _ + } } } /// Get an iterator over all of the candidates of a variable. - fn iter<'a>(&'a self) -> Box + 'a> { + fn iter(&self) -> Box + '_> { match self { Candidates::None => Box::new(iter::empty()), Candidates::Value(val) => Box::new(iter::once(*val)), Candidates::Set(ref rc) => Box::new(rc.iter().cloned()), + Candidates::Range(range) => { + Box::new(range.as_slice().iter().map(|x| x.into_iter()).flatten()) + } } } } @@ -138,6 +167,21 @@ impl Puzzle { var } + /// Allocate a new puzzle variable, initialising it with potential + /// candidates. + /// + /// # Examples + /// + /// ``` + /// let mut send_more_money = puzzle_solver::Puzzle::new(); + /// send_more_money.new_var_with_candidates(&[0,1,2,3,4,5,6,7,8,9]); + /// ``` + pub fn new_var_with_range>(&mut self, range: R) -> VarToken { + let var = self.new_var(); + self.insert_range(var, range); + var + } + /// Allocate a 1d vector of puzzle variables, each initialised to /// have the same set of potential candidates. /// @@ -155,6 +199,27 @@ impl Puzzle { vars } + /// Allocate a 1d vector of puzzle variables, each initialised to + /// have the same set of potential candidates. + /// + /// # Examples + /// + /// ``` + /// 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_range_1d + Clone>( + &mut self, + n: usize, + range: R, + ) -> Vec { + let mut vars = Vec::with_capacity(n); + for _ in 0..n { + vars.push(self.new_var_with_range(range.clone())); + } + vars + } + /// Allocate a 2d array of puzzle variables, each initialised to /// have the same set of potential candidates. /// @@ -177,6 +242,28 @@ impl Puzzle { vars } + /// Allocate a 2d array of puzzle variables, each initialised to + /// have the same set of potential candidates. + /// + /// # Examples + /// + /// ``` + /// 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_range_2d + Clone>( + self: &mut Puzzle, + width: usize, + height: usize, + range: R, + ) -> Vec> { + let mut vars = Vec::with_capacity(height); + for _ in 0..height { + vars.push(self.new_vars_with_range_1d(width, range.clone())); + } + vars + } + /// Set a variable to a known value. /// /// This is useful when the variable is given as part of the @@ -220,6 +307,7 @@ impl Puzzle { match self.candidates[idx] { Candidates::Value(_) => panic!("attempt to set fixed variable"), + Candidates::Range(_) => panic!("attempt to insert candidates into range"), Candidates::None => { self.candidates[idx] = Candidates::Set(Rc::new(BTreeSet::new())); @@ -252,10 +340,9 @@ impl Puzzle { let VarToken(idx) = var; match self.candidates[idx] { - Candidates::Value(_) => panic!("attempt to set fixed variable"), - Candidates::None => (), - + Candidates::Value(_) => panic!("attempt to set fixed variable"), + Candidates::Range(_) => panic!("attempt to remove candidates from range"), Candidates::Set(ref mut rc) => { let cs = Rc::get_mut(rc).expect("unique"); for c in candidates.iter() { @@ -282,10 +369,9 @@ impl Puzzle { let VarToken(idx) = var; match self.candidates[idx] { - Candidates::Value(_) => panic!("attempt to set fixed variable"), - Candidates::None => (), - + Candidates::Value(_) => panic!("attempt to set fixed variable"), + Candidates::Range(_) => panic!("attempt to intersect candidates on the range"), Candidates::Set(ref mut rc) => { let cs = Rc::get_mut(rc).expect("unique"); let mut set = BTreeSet::new(); @@ -295,6 +381,31 @@ impl Puzzle { } } + /// + /// Insert range + /// + /// + pub fn insert_range>(&mut self, var: VarToken, range: R) { + let VarToken(idx) = var; + + match self.candidates[idx] { + ref mut x @ Candidates::None => { + *x = Candidates::Range(Ranges::from(( + range.start_bound().cloned(), + range.end_bound().cloned(), + ))); + } + Candidates::Value(_) => panic!("attempt to set fixed variable"), + Candidates::Range(ref mut r) => { + r.insert(GenericRange::new_with_bounds( + range.start_bound().cloned(), + range.end_bound().cloned(), + )); + } + Candidates::Set(_) => panic!("attempt to insert range on set"), + } + } + /// Add a constraint to the puzzle solution. pub fn add_constraint(&mut self, constraint: T) where @@ -570,6 +681,28 @@ impl<'a> PuzzleSearch<'a> { VarState::Unassigned(ref cs) => match cs { Candidates::None => Err(Error::Default), Candidates::Value(val) => Ok((*val, *val)), + Candidates::Range(r) => { + let slice = r.as_slice(); + let first = slice.get(0).ok_or(Error::Default)?.start_bound(); + let last = slice + .get(slice.len() - 1) + .ok_or(Error::Default)? + .end_bound(); + + let min = match first { + Bound::Included(val) => *val, + Bound::Excluded(val) => val + 1, + Bound::Unbounded => unreachable!(), + }; + + let max = match last { + Bound::Included(val) => *val, + Bound::Excluded(val) => val - 1, + Bound::Unbounded => unreachable!(), + }; + + Ok((min, max)) + } Candidates::Set(ref rc) => rc .iter() .cloned() @@ -587,30 +720,34 @@ impl<'a> PuzzleSearch<'a> { pub fn set_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> { let VarToken(idx) = var; - match &self.vars[idx] { - VarState::Assigned(v) => return bool_to_result(*v == val), - VarState::Unassigned(ref cs) => match cs { - Candidates::None => return Err(Error::Default), - Candidates::Value(v) => return bool_to_result(*v == val), - Candidates::Set(_) => (), - }, - VarState::Unified(_) => (), - } + match self.vars[idx] { + VarState::Assigned(v) => bool_to_result(v == val), + VarState::Unassigned(ref mut cs) => match cs { + Candidates::None => Err(Error::Default), + Candidates::Value(v) => bool_to_result(*v == val), + Candidates::Range(ref mut r) => { + if r.contains(&val) { + *r = Ranges::from(val..=val); + self.wake.union_with(&self.constraints.wake[idx]); - if let VarState::Unified(other) = self.vars[idx] { - self.set_candidate(other, val) - } else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] { - if rc.contains(&val) { - let set = Rc::make_mut(rc); - set.clear(); - set.insert(val); - self.wake.union_with(&self.constraints.wake[idx]); - Ok(()) - } else { - Err(Error::Default) - } - } else { - unreachable!(); + Ok(()) + } else { + Err(Error::Default) + } + } + Candidates::Set(rc) => { + if rc.contains(&val) { + let set = Rc::make_mut(rc); + set.clear(); + set.insert(val); + self.wake.union_with(&self.constraints.wake[idx]); + Ok(()) + } else { + Err(Error::Default) + } + } + }, + VarState::Unified(other) => self.set_candidate(other, val), } } @@ -618,27 +755,29 @@ impl<'a> PuzzleSearch<'a> { pub fn remove_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> { let VarToken(idx) = var; - match &self.vars[idx] { - VarState::Assigned(v) => return bool_to_result(*v != val), - VarState::Unassigned(ref cs) => match cs { - Candidates::None => return Err(Error::Default), - Candidates::Value(v) => return bool_to_result(*v != val), - Candidates::Set(_) => (), - }, - VarState::Unified(_) => (), - } + match self.vars[idx] { + VarState::Assigned(v) => bool_to_result(v != val), + VarState::Unassigned(ref mut cs) => match cs { + Candidates::None => Err(Error::Default), + Candidates::Value(v) => bool_to_result(*v != val), + Candidates::Range(r) => { + if r.contains(&val) { + r.remove(val..=val); + self.wake.union_with(&self.constraints.wake[idx]); + } - if let VarState::Unified(other) = self.vars[idx] { - self.remove_candidate(other, val) - } else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] { - if rc.contains(&val) { - let set = Rc::make_mut(rc); - set.remove(&val); - self.wake.union_with(&self.constraints.wake[idx]); - } - bool_to_result(!rc.is_empty()) - } else { - unreachable!(); + bool_to_result(!r.is_empty()) + } + Candidates::Set(rc) => { + if rc.contains(&val) { + let set = Rc::make_mut(rc); + set.remove(&val); + self.wake.union_with(&self.constraints.wake[idx]); + } + bool_to_result(!rc.is_empty()) + } + }, + VarState::Unified(other) => self.remove_candidate(other, val), } } @@ -654,51 +793,77 @@ impl<'a> PuzzleSearch<'a> { match self.vars[idx] { VarState::Assigned(v) => { if min <= v && v <= max { - return Ok((v, v)); + Ok((v, v)) } else { - return Err(Error::Default); + Err(Error::Default) } } - VarState::Unassigned(ref cs) => match cs { - Candidates::None => return Err(Error::Default), + + VarState::Unassigned(ref mut cs) => match cs { + Candidates::None => Err(Error::Default), Candidates::Value(v) => { if min <= *v && *v <= max { - return Ok((*v, *v)); + Ok((*v, *v)) } else { - return Err(Error::Default); + Err(Error::Default) + } + } + Candidates::Range(ref mut r) => { + if max < min { + return Err(Error::Default); + } + + *r = r.clone().intersect(min..=max); + + self.wake.union_with(&self.constraints.wake[idx]); + + let slice = r.as_slice(); + let first = slice.get(0).ok_or(Error::Default)?.start_bound(); + let last = slice + .get(slice.len() - 1) + .ok_or(Error::Default)? + .end_bound(); + + let min = match first { + Bound::Included(val) => *val, + Bound::Excluded(val) => val + 1, + Bound::Unbounded => unreachable!(), + }; + + let max = match last { + Bound::Included(val) => *val, + Bound::Excluded(val) => val - 1, + Bound::Unbounded => unreachable!(), + }; + + Ok((min, max)) + } + Candidates::Set(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 set = Rc::make_mut(rc); + *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() + .ok_or(Error::Default) + } else { + Ok((curr_min, curr_max)) } } - Candidates::Set(_) => (), }, - VarState::Unified(_) => (), - } - if let VarState::Unified(other) = self.vars[idx] { - self.bound_candidate_range(other, min, max) - } else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = 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 set = Rc::make_mut(rc); - *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() - .ok_or(Error::Default) - } else { - Ok((curr_min, curr_max)) - } - } else { - unreachable!(); + VarState::Unified(other) => self.bound_candidate_range(other, min, max), } } diff --git a/tests/sudoku.rs b/tests/sudoku.rs index ae8e7a7..26e80d0 100644 --- a/tests/sudoku.rs +++ b/tests/sudoku.rs @@ -12,7 +12,7 @@ type Board = [[Val; SIZE]; SIZE]; fn make_sudoku(board: &Board) -> (Puzzle, Vec>) { let mut sys = Puzzle::new(); - let vars = sys.new_vars_with_candidates_2d(SIZE, SIZE, &[1, 2, 3, 4, 5, 6, 7, 8, 9]); + let vars = sys.new_vars_with_range_2d(SIZE, SIZE, 1..=9); for y in 0..SIZE { sys.all_different(&vars[y]); diff --git a/tests/takuzu.rs b/tests/takuzu.rs index 6ba6994..f4c8438 100644 --- a/tests/takuzu.rs +++ b/tests/takuzu.rs @@ -60,8 +60,8 @@ fn make_sums(size: usize) -> Vec { let mut v = val as usize; while v > 0 { - count = count + (v & 1); - v = v >> 1; + count += (v & 1); + v >>= 1; } if count == size / 2 {