diff --git a/Cargo.toml b/Cargo.toml index 3e9cefe..84825b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,6 @@ thiserror = "1.0.30" [badges] travis-ci = { repository = "wangds/puzzle-solver" } + +[dev-dependencies] +drawille = "0.3.0" diff --git a/src/lib.rs b/src/lib.rs index 34477ec..ff41472 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod constraint; mod error; mod linexpr; mod puzzle; +mod ranges; use core::fmt; use num_rational::Rational32; diff --git a/src/puzzle.rs b/src/puzzle.rs index f9e6529..c4a1dd1 100644 --- a/src/puzzle.rs +++ b/src/puzzle.rs @@ -1,14 +1,12 @@ //! 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; @@ -16,7 +14,7 @@ use crate::constraint; use crate::Error; use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken}; -use ranges::Ranges; +use crate::ranges::Ranges; /// A collection of candidates. #[derive(Clone, Debug, Eq, PartialEq)] @@ -24,7 +22,7 @@ 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. + Range(Ranges), // A variable with candidate ranges. } /// The state of a variable during the solution search. @@ -81,26 +79,7 @@ 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 _ - } + Candidates::Range(rc) => rc.len(), } } @@ -110,9 +89,7 @@ impl Candidates { 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()) - } + Candidates::Range(range) => Box::new(range.iter()), } } } @@ -390,18 +367,15 @@ impl Puzzle { 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( + *x = Candidates::Range(Ranges::new( range.start_bound().cloned(), range.end_bound().cloned(), )); } + Candidates::Value(_) => panic!("attempt to set fixed variable"), + Candidates::Range(ref mut r) => { + r.insert(range.start_bound().cloned(), range.end_bound().cloned()); + } Candidates::Set(_) => panic!("attempt to insert range on set"), } } @@ -664,7 +638,7 @@ impl<'a> PuzzleSearch<'a> { } /// Get an iterator over the candidates to an unassigned variable. - pub fn get_unassigned(&'a self, var: VarToken) -> Box + 'a> { + pub fn get_unassigned(&self, var: VarToken) -> Box + '_> { let VarToken(idx) = var; match &self.vars[idx] { VarState::Assigned(_) => Box::new(iter::empty()), @@ -681,28 +655,7 @@ 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::Range(r) => r.get_bounds().ok_or(Error::Default), Candidates::Set(ref rc) => rc .iter() .cloned() @@ -726,8 +679,8 @@ impl<'a> PuzzleSearch<'a> { 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); + if r.contains(val) { + *r = (val..=val).into(); self.wake.union_with(&self.constraints.wake[idx]); Ok(()) @@ -761,7 +714,7 @@ impl<'a> PuzzleSearch<'a> { Candidates::None => Err(Error::Default), Candidates::Value(v) => bool_to_result(*v != val), Candidates::Range(r) => { - if r.contains(&val) { + if r.contains(val) { r.remove(val..=val); self.wake.union_with(&self.constraints.wake[idx]); } @@ -808,36 +761,19 @@ impl<'a> PuzzleSearch<'a> { Err(Error::Default) } } + Candidates::Range(ref mut r) => { if max < min { return Err(Error::Default); } - *r = r.clone().intersect(min..=max); + r.intersection(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)) + r.get_bounds().ok_or(Error::Default) } + Candidates::Set(rc) => { let &curr_min = rc.iter().min().expect("candidates"); let &curr_max = rc.iter().max().expect("candidates"); @@ -1029,7 +965,7 @@ impl<'a> PuzzleSearch<'a> { } } -impl<'a> fmt::Debug for PuzzleSearch<'a> { +impl fmt::Debug for PuzzleSearch<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "PuzzleSearch={{")?; for (idx, var) in self.vars.iter().enumerate() { @@ -1055,7 +991,7 @@ impl<'a> fmt::Debug for PuzzleSearch<'a> { } } -impl<'a> ops::Index for PuzzleSearch<'a> { +impl ops::Index for PuzzleSearch<'_> { type Output = Val; /// Get the value assigned to a variable. diff --git a/src/ranges.rs b/src/ranges.rs new file mode 100644 index 0000000..f1ffc1a --- /dev/null +++ b/src/ranges.rs @@ -0,0 +1,121 @@ +use std::ops::{Bound, Range, RangeBounds, RangeInclusive}; + +use ranges::GenericRange; + +use crate::Val; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Ranges { + inner: ranges::Ranges, +} + +impl Ranges { + pub fn new(from: Bound, to: Bound) -> Self { + Self { + inner: ranges::Ranges::from((from, to)), + } + } + + #[inline] + pub fn intersection>(&mut self, other: R) { + let left = std::mem::replace(&mut self.inner, Default::default()); + self.inner = left & other.into().inner; + } + + #[inline] + pub fn contains(&self, val: Val) -> bool { + self.inner.contains(&val) + } + + #[inline] + pub fn remove>(&mut self, range: R) { + self.inner.remove(GenericRange::new_with_bounds( + range.start_bound().cloned(), + range.end_bound().cloned(), + )); + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn len(&self) -> usize { + let mut total = 0; + + for i in self.inner.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 _ + } + + pub fn get_bounds(&self) -> Option<(Val, Val)> { + let slice = self.inner.as_slice(); + let first = slice.get(0)?.start_bound(); + let last = slice.get(slice.len() - 1)?.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!(), + }; + + Some((min, max)) + } + + #[inline] + pub fn insert(&mut self, from: Bound, to: Bound) { + self.inner.insert(GenericRange::new_with_bounds(from, to)); + } + + pub fn iter(&self) -> impl Iterator + '_ { + self.inner + .as_slice() + .iter() + .map(|x| x.into_iter()) + .flatten() + } +} + +impl From<(Bound, Bound)> for Ranges { + fn from(range: (Bound, Bound)) -> Self { + Self { + inner: range.into(), + } + } +} + +impl From> for Ranges { + fn from(range: RangeInclusive) -> Self { + Self { + inner: range.into(), + } + } +} + +impl From> for Ranges { + fn from(range: Range) -> Self { + Self { + inner: range.into(), + } + } +} diff --git a/tests/nonogram.rs b/tests/nonogram.rs index 6d61baf..f8ee245 100644 --- a/tests/nonogram.rs +++ b/tests/nonogram.rs @@ -18,6 +18,7 @@ const FLAG_OFF: Val = 1; const FLAG_ON: Val = 2; const FLAG_UNKNOWN: Val = 4; +#[derive(Debug)] struct Nonogram { vars: Vec, rule: Vec, @@ -146,7 +147,7 @@ impl Nonogram { } impl Constraint for Nonogram { - fn vars<'a>(&'a self) -> Box + 'a> { + fn vars(&self) -> Box + '_> { Box::new(self.vars.iter()) } @@ -203,13 +204,18 @@ fn make_nonogram(rows: &[Vec], cols: &[Vec]) -> (Puzzle, Vec]) { - for row in vars.iter() { - for &var in row.iter() { - print!("{}", if dict[var] == 1 { "*" } else { "." }); +fn print_nonogram(dict: &Solution, w: u32, h: u32, vars: &[Vec]) { + let mut canvas = drawille::Canvas::new(w, h); + + for (y, row) in vars.iter().enumerate() { + for (x, &var) in row.iter().enumerate() { + if dict[var] == 1 { + canvas.set(x as _, y as _); + } } - println!(); } + + println!("{}", canvas.frame()); } fn verify_nonogram(dict: &Solution, vars: &[Vec], expected: &Board) { @@ -293,7 +299,7 @@ fn nonogram_wikipedia() { let (mut sys, vars) = make_nonogram(&puzzle_rows, &puzzle_cols); let dict = sys.solve_unique().expect("solution"); - print_nonogram(&dict, &vars); + print_nonogram(&dict, puzzle_cols.len() as _, puzzle_rows.len() as _, &vars); verify_nonogram(&dict, &vars, &expected); println!("nonogram_wikipedia: {} guesses.", sys.num_guesses()); } diff --git a/tests/queens.rs b/tests/queens.rs index fc63788..0bc5ef9 100644 --- a/tests/queens.rs +++ b/tests/queens.rs @@ -7,12 +7,13 @@ extern crate puzzle_solver; use puzzle_solver::*; use std::rc::Rc; +#[derive(Debug)] struct NoDiagonal { vars: Vec, } impl Constraint for NoDiagonal { - fn vars<'a>(&'a self) -> Box + 'a> { + fn vars(&self) -> Box + '_> { Box::new(self.vars.iter()) } diff --git a/tests/takuzu.rs b/tests/takuzu.rs index f4c8438..5c9544a 100644 --- a/tests/takuzu.rs +++ b/tests/takuzu.rs @@ -11,7 +11,7 @@ use std::rc::Rc; const X: Val = -1; /*--------------------------------------------------------------*/ - +#[derive(Debug)] struct BinaryRepr { // value = sum_i 2^i bits[i] value: VarToken, @@ -60,7 +60,7 @@ fn make_sums(size: usize) -> Vec { let mut v = val as usize; while v > 0 { - count += (v & 1); + count += v & 1; v >>= 1; }