Split out Ranges struct
This commit is contained in:
parent
ba86c49bfb
commit
e363a552f0
@ -25,3 +25,6 @@ thiserror = "1.0.30"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "wangds/puzzle-solver" }
|
||||
|
||||
[dev-dependencies]
|
||||
drawille = "0.3.0"
|
||||
|
@ -6,6 +6,7 @@ pub mod constraint;
|
||||
mod error;
|
||||
mod linexpr;
|
||||
mod puzzle;
|
||||
mod ranges;
|
||||
|
||||
use core::fmt;
|
||||
use num_rational::Rational32;
|
||||
|
104
src/puzzle.rs
104
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<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
121
src/ranges.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
@ -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,13 +204,18 @@ 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) {
|
||||
@ -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());
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user