Split out Ranges struct

This commit is contained in:
Andrey Tkachenko 2021-12-09 22:26:07 +04:00
parent ba86c49bfb
commit e363a552f0
7 changed files with 162 additions and 94 deletions

View File

@ -25,3 +25,6 @@ thiserror = "1.0.30"
[badges]
travis-ci = { repository = "wangds/puzzle-solver" }
[dev-dependencies]
drawille = "0.3.0"

View File

@ -6,6 +6,7 @@ pub mod constraint;
mod error;
mod linexpr;
mod puzzle;
mod ranges;
use core::fmt;
use num_rational::Rational32;

View File

@ -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<BTreeSet<Val>>), // A variable with a list of candidates.
Range(Ranges<Val>), // 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<dyn Iterator<Item = Val> + 'a> {
pub fn get_unassigned(&self, var: VarToken) -> Box<dyn Iterator<Item = Val> + '_> {
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<VarToken> for PuzzleSearch<'a> {
impl ops::Index<VarToken> for PuzzleSearch<'_> {
type Output = Val;
/// Get the value assigned to a variable.

121
src/ranges.rs Normal file
View File

@ -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<Val>,
}
impl Ranges {
pub fn new(from: Bound<Val>, to: Bound<Val>) -> Self {
Self {
inner: ranges::Ranges::from((from, to)),
}
}
#[inline]
pub fn intersection<R: Into<Ranges>>(&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<R: RangeBounds<Val>>(&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<Val>, to: Bound<Val>) {
self.inner.insert(GenericRange::new_with_bounds(from, to));
}
pub fn iter(&self) -> impl Iterator<Item = Val> + '_ {
self.inner
.as_slice()
.iter()
.map(|x| x.into_iter())
.flatten()
}
}
impl From<(Bound<Val>, Bound<Val>)> for Ranges {
fn from(range: (Bound<Val>, Bound<Val>)) -> Self {
Self {
inner: range.into(),
}
}
}
impl From<RangeInclusive<Val>> for Ranges {
fn from(range: RangeInclusive<Val>) -> Self {
Self {
inner: range.into(),
}
}
}
impl From<Range<Val>> for Ranges {
fn from(range: Range<Val>) -> Self {
Self {
inner: range.into(),
}
}
}

View File

@ -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<VarToken>,
rule: Vec<usize>,
@ -146,7 +147,7 @@ impl Nonogram {
}
impl Constraint for Nonogram {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> {
fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_> {
Box::new(self.vars.iter())
}
@ -203,15 +204,20 @@ fn make_nonogram(rows: &[Vec<usize>], cols: &[Vec<usize>]) -> (Puzzle, Vec<Vec<V
(sys, vars)
}
fn print_nonogram(dict: &Solution, vars: &[Vec<VarToken>]) {
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<VarToken>]) {
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<VarToken>], expected: &Board) {
for y in 0..HEIGHT {
for x in 0..WIDTH {
@ -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());
}

View File

@ -7,12 +7,13 @@ extern crate puzzle_solver;
use puzzle_solver::*;
use std::rc::Rc;
#[derive(Debug)]
struct NoDiagonal {
vars: Vec<VarToken>,
}
impl Constraint for NoDiagonal {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> {
fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_> {
Box::new(self.vars.iter())
}

View File

@ -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<Val> {
let mut v = val as usize;
while v > 0 {
count += (v & 1);
count += v & 1;
v >>= 1;
}