This commit is contained in:
Andrey Tkachenko 2021-12-08 18:50:21 +04:00
parent 631d25cc3c
commit ba86c49bfb
9 changed files with 318 additions and 121 deletions

View File

@ -20,6 +20,7 @@ path = "./bin/run.rs"
bit-set = "0.4" bit-set = "0.4"
num-rational = { version = "0.1", default-features = false } num-rational = { version = "0.1", default-features = false }
num-traits = "0.1" num-traits = "0.1"
ranges = "0.3.3"
thiserror = "1.0.30" thiserror = "1.0.30"
[badges] [badges]

View File

@ -1,10 +1,11 @@
//! All different implementation. //! All different implementation.
use std::collections::HashMap; use std::collections::{hash_map::Entry, HashMap};
use std::rc::Rc; use std::rc::Rc;
use crate::{Constraint, Error, PsResult, PuzzleSearch, Val, VarToken}; use crate::{Constraint, Error, PsResult, PuzzleSearch, Val, VarToken};
#[derive(Debug)]
pub struct AllDifferent { pub struct AllDifferent {
vars: Vec<VarToken>, vars: Vec<VarToken>,
} }
@ -32,7 +33,7 @@ impl AllDifferent {
} }
impl Constraint for AllDifferent { impl Constraint for AllDifferent {
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()) Box::new(self.vars.iter())
} }
@ -54,10 +55,11 @@ impl Constraint for AllDifferent {
for val in search.get_unassigned(var) { for val in search.get_unassigned(var) {
match all_candidates.entry(val) { match all_candidates.entry(val) {
std::collections::hash_map::Entry::Occupied(mut e) => { Entry::Occupied(mut e) => {
e.insert(None); e.insert(None);
} }
std::collections::hash_map::Entry::Vacant(e) => {
Entry::Vacant(e) => {
e.insert(Some(var)); e.insert(Some(var));
} }
} }

View File

@ -6,6 +6,7 @@ use std::rc::Rc;
use crate::{Constraint, Error, LinExpr, PsResult, PuzzleSearch, Val, VarToken}; use crate::{Constraint, Error, LinExpr, PsResult, PuzzleSearch, Val, VarToken};
#[derive(Debug)]
pub struct Equality { pub struct Equality {
// The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ... // The equation: 0 = constant + coef1 * var1 + coef2 * var2 + ...
eqn: LinExpr, eqn: LinExpr,
@ -30,11 +31,11 @@ impl Equality {
} }
impl Constraint for Equality { impl Constraint for Equality {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> { fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_> {
Box::new(self.eqn.coef.keys()) 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 sum = self.eqn.constant;
let mut unassigned_var = None; 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_val, max_val) = search.get_min_max(var)?;
let (min_bnd, max_bnd); let (min_bnd, max_bnd) = if coef > Ratio::zero() {
(
if coef > Ratio::zero() { ((coef * Ratio::from_integer(max_val) - sum_max) / coef)
min_bnd = ((coef * Ratio::from_integer(max_val) - sum_max) / coef)
.ceil() .ceil()
.to_integer(); .to_integer(),
max_bnd = ((coef * Ratio::from_integer(min_val) - sum_min) / coef) ((coef * Ratio::from_integer(min_val) - sum_min) / coef)
.floor() .floor()
.to_integer(); .to_integer(),
)
} else { } else {
min_bnd = ((coef * Ratio::from_integer(max_val) - sum_min) / coef) (
((coef * Ratio::from_integer(max_val) - sum_min) / coef)
.ceil() .ceil()
.to_integer(); .to_integer(),
max_bnd = ((coef * Ratio::from_integer(min_val) - sum_max) / coef) ((coef * Ratio::from_integer(min_val) - sum_max) / coef)
.floor() .floor()
.to_integer(); .to_integer(),
} )
};
if min_val < min_bnd || max_bnd < max_val { if min_val < min_bnd || max_bnd < max_val {
let (new_min, new_max) = search.bound_candidate_range(var, min_bnd, max_bnd)?; let (new_min, new_max) = search.bound_candidate_range(var, min_bnd, max_bnd)?;

View File

@ -5,14 +5,15 @@
//! cannot store additional information about the state (e.g. caches) //! cannot store additional information about the state (e.g. caches)
//! in the constraint to reuse later. //! in the constraint to reuse later.
use std::fmt::Debug;
use std::rc::Rc; use std::rc::Rc;
use crate::{PsResult, PuzzleSearch, Val, VarToken}; use crate::{PsResult, PuzzleSearch, Val, VarToken};
/// Constraint trait. /// Constraint trait.
pub trait Constraint { pub trait Constraint: Debug {
/// An iterator over the variables that are involved in the constraint. /// An iterator over the variables that are involved in the constraint.
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a>; fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_>;
/// Applied after a variable has been assigned. /// Applied after a variable has been assigned.
fn on_assigned(&self, _search: &mut PuzzleSearch, _var: VarToken, _val: Val) -> PsResult<()> { fn on_assigned(&self, _search: &mut PuzzleSearch, _var: VarToken, _val: Val) -> PsResult<()> {

View File

@ -5,6 +5,7 @@ use std::rc::Rc;
use crate::{Constraint, PsResult, PuzzleSearch, VarToken}; use crate::{Constraint, PsResult, PuzzleSearch, VarToken};
#[derive(Debug)]
pub struct Unify { pub struct Unify {
var1: VarToken, var1: VarToken,
var2: VarToken, var2: VarToken,
@ -25,15 +26,12 @@ impl Unify {
/// puzzle_solver::constraint::Unify::new(m, carry[3]); /// puzzle_solver::constraint::Unify::new(m, carry[3]);
/// ``` /// ```
pub fn new(var1: VarToken, var2: VarToken) -> Self { pub fn new(var1: VarToken, var2: VarToken) -> Self {
Unify { Unify { var1, var2 }
var1,
var2,
}
} }
} }
impl Constraint for Unify { impl Constraint for Unify {
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> { fn vars(&self) -> Box<dyn Iterator<Item = &'_ VarToken> + '_> {
if self.var1 != self.var2 { if self.var1 != self.var2 {
Box::new(iter::once(&self.var1).chain(iter::once(&self.var2))) Box::new(iter::once(&self.var1).chain(iter::once(&self.var2)))
} else { } else {
@ -52,10 +50,7 @@ impl Constraint for Unify {
fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>> { fn substitute(&self, from: VarToken, to: VarToken) -> PsResult<Rc<dyn Constraint>> {
let var1 = if self.var1 == from { to } else { self.var1 }; let var1 = if self.var1 == from { to } else { self.var1 };
let var2 = if self.var2 == from { to } else { self.var2 }; let var2 = if self.var2 == from { to } else { self.var2 };
Ok(Rc::new(Unify { Ok(Rc::new(Unify { var1, var2 }))
var1,
var2,
}))
} }
} }

View File

@ -7,7 +7,9 @@ mod error;
mod linexpr; mod linexpr;
mod puzzle; mod puzzle;
use core::fmt;
use num_rational::Rational32; use num_rational::Rational32;
use num_traits::Signed;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops; use std::ops;
@ -41,6 +43,34 @@ pub struct LinExpr {
coef: HashMap<VarToken, Coef>, coef: HashMap<VarToken, Coef>,
} }
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). /// A result during a puzzle solution search (Err = contradiction).
pub type PsResult<T> = Result<T, Error>; pub type PsResult<T> = Result<T, Error>;

View File

@ -1,24 +1,30 @@
//! The puzzle's state and rules. //! The puzzle's state and rules.
use bit_set::BitSet; use bit_set::BitSet;
use ranges::GenericRange;
use std::cell::Cell; use std::cell::Cell;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt; use std::fmt;
use std::iter; use std::iter;
use std::mem; use std::mem;
use std::ops; use std::ops;
use std::ops::Bound;
use std::ops::RangeBounds;
use std::rc::Rc; use std::rc::Rc;
use crate::constraint; use crate::constraint;
use crate::Error; use crate::Error;
use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken}; use crate::{Constraint, LinExpr, PsResult, Solution, Val, VarToken};
use ranges::Ranges;
/// A collection of candidates. /// A collection of candidates.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
enum Candidates { enum Candidates {
None, // A variable with no candidates. None, // A variable with no candidates.
Value(Val), // A variable set to its initial value. Value(Val), // A variable set to its initial value.
Set(Rc<BTreeSet<Val>>), // A variable with a list of candidates. Set(Rc<BTreeSet<Val>>), // A variable with a list of candidates.
Range(Ranges<Val>), // A variable with candidate ranges.
} }
/// The state of a variable during the solution search. /// The state of a variable during the solution search.
@ -75,15 +81,38 @@ impl Candidates {
Candidates::None => 0, Candidates::None => 0,
Candidates::Value(_) => 1, Candidates::Value(_) => 1,
Candidates::Set(ref rc) => rc.len(), 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. /// Get an iterator over all of the candidates of a variable.
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = Val> + 'a> { fn iter(&self) -> Box<dyn Iterator<Item = Val> + '_> {
match self { match self {
Candidates::None => Box::new(iter::empty()), Candidates::None => Box::new(iter::empty()),
Candidates::Value(val) => Box::new(iter::once(*val)), Candidates::Value(val) => Box::new(iter::once(*val)),
Candidates::Set(ref rc) => Box::new(rc.iter().cloned()), 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 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<R: RangeBounds<Val>>(&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 /// Allocate a 1d vector of puzzle variables, each initialised to
/// have the same set of potential candidates. /// have the same set of potential candidates.
/// ///
@ -155,6 +199,27 @@ impl Puzzle {
vars 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<R: RangeBounds<Val> + Clone>(
&mut self,
n: usize,
range: R,
) -> Vec<VarToken> {
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 /// Allocate a 2d array of puzzle variables, each initialised to
/// have the same set of potential candidates. /// have the same set of potential candidates.
/// ///
@ -177,6 +242,28 @@ impl Puzzle {
vars 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<R: RangeBounds<Val> + Clone>(
self: &mut Puzzle,
width: usize,
height: usize,
range: R,
) -> Vec<Vec<VarToken>> {
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. /// Set a variable to a known value.
/// ///
/// This is useful when the variable is given as part of the /// This is useful when the variable is given as part of the
@ -220,6 +307,7 @@ impl Puzzle {
match self.candidates[idx] { match self.candidates[idx] {
Candidates::Value(_) => panic!("attempt to set fixed variable"), Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::Range(_) => panic!("attempt to insert candidates into range"),
Candidates::None => { Candidates::None => {
self.candidates[idx] = Candidates::Set(Rc::new(BTreeSet::new())); self.candidates[idx] = Candidates::Set(Rc::new(BTreeSet::new()));
@ -252,10 +340,9 @@ impl Puzzle {
let VarToken(idx) = var; let VarToken(idx) = var;
match self.candidates[idx] { match self.candidates[idx] {
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::None => (), Candidates::None => (),
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::Range(_) => panic!("attempt to remove candidates from range"),
Candidates::Set(ref mut rc) => { Candidates::Set(ref mut rc) => {
let cs = Rc::get_mut(rc).expect("unique"); let cs = Rc::get_mut(rc).expect("unique");
for c in candidates.iter() { for c in candidates.iter() {
@ -282,10 +369,9 @@ impl Puzzle {
let VarToken(idx) = var; let VarToken(idx) = var;
match self.candidates[idx] { match self.candidates[idx] {
Candidates::Value(_) => panic!("attempt to set fixed variable"),
Candidates::None => (), 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) => { Candidates::Set(ref mut rc) => {
let cs = Rc::get_mut(rc).expect("unique"); let cs = Rc::get_mut(rc).expect("unique");
let mut set = BTreeSet::new(); let mut set = BTreeSet::new();
@ -295,6 +381,31 @@ impl Puzzle {
} }
} }
///
/// Insert range
///
///
pub fn insert_range<R: RangeBounds<Val>>(&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. /// Add a constraint to the puzzle solution.
pub fn add_constraint<T>(&mut self, constraint: T) pub fn add_constraint<T>(&mut self, constraint: T)
where where
@ -570,6 +681,28 @@ impl<'a> PuzzleSearch<'a> {
VarState::Unassigned(ref cs) => match cs { VarState::Unassigned(ref cs) => match cs {
Candidates::None => Err(Error::Default), Candidates::None => Err(Error::Default),
Candidates::Value(val) => Ok((*val, *val)), 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 Candidates::Set(ref rc) => rc
.iter() .iter()
.cloned() .cloned()
@ -587,19 +720,22 @@ impl<'a> PuzzleSearch<'a> {
pub fn set_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> { pub fn set_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> {
let VarToken(idx) = var; let VarToken(idx) = var;
match &self.vars[idx] { match self.vars[idx] {
VarState::Assigned(v) => return bool_to_result(*v == val), VarState::Assigned(v) => bool_to_result(v == val),
VarState::Unassigned(ref cs) => match cs { VarState::Unassigned(ref mut cs) => match cs {
Candidates::None => return Err(Error::Default), Candidates::None => Err(Error::Default),
Candidates::Value(v) => return bool_to_result(*v == val), Candidates::Value(v) => bool_to_result(*v == val),
Candidates::Set(_) => (), Candidates::Range(ref mut r) => {
}, if r.contains(&val) {
VarState::Unified(_) => (), *r = Ranges::from(val..=val);
} self.wake.union_with(&self.constraints.wake[idx]);
if let VarState::Unified(other) = self.vars[idx] { Ok(())
self.set_candidate(other, val) } else {
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] { Err(Error::Default)
}
}
Candidates::Set(rc) => {
if rc.contains(&val) { if rc.contains(&val) {
let set = Rc::make_mut(rc); let set = Rc::make_mut(rc);
set.clear(); set.clear();
@ -609,8 +745,9 @@ impl<'a> PuzzleSearch<'a> {
} else { } else {
Err(Error::Default) Err(Error::Default)
} }
} else { }
unreachable!(); },
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<()> { pub fn remove_candidate(&mut self, var: VarToken, val: Val) -> PsResult<()> {
let VarToken(idx) = var; let VarToken(idx) = var;
match &self.vars[idx] { match self.vars[idx] {
VarState::Assigned(v) => return bool_to_result(*v != val), VarState::Assigned(v) => bool_to_result(v != val),
VarState::Unassigned(ref cs) => match cs { VarState::Unassigned(ref mut cs) => match cs {
Candidates::None => return Err(Error::Default), Candidates::None => Err(Error::Default),
Candidates::Value(v) => return bool_to_result(*v != val), Candidates::Value(v) => bool_to_result(*v != val),
Candidates::Set(_) => (), Candidates::Range(r) => {
}, if r.contains(&val) {
VarState::Unified(_) => (), r.remove(val..=val);
self.wake.union_with(&self.constraints.wake[idx]);
} }
if let VarState::Unified(other) = self.vars[idx] { bool_to_result(!r.is_empty())
self.remove_candidate(other, val) }
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] { Candidates::Set(rc) => {
if rc.contains(&val) { if rc.contains(&val) {
let set = Rc::make_mut(rc); let set = Rc::make_mut(rc);
set.remove(&val); set.remove(&val);
self.wake.union_with(&self.constraints.wake[idx]); self.wake.union_with(&self.constraints.wake[idx]);
} }
bool_to_result(!rc.is_empty()) bool_to_result(!rc.is_empty())
} else { }
unreachable!(); },
VarState::Unified(other) => self.remove_candidate(other, val),
} }
} }
@ -654,28 +793,52 @@ impl<'a> PuzzleSearch<'a> {
match self.vars[idx] { match self.vars[idx] {
VarState::Assigned(v) => { VarState::Assigned(v) => {
if min <= v && v <= max { if min <= v && v <= max {
return Ok((v, v)); Ok((v, v))
} else { } else {
return Err(Error::Default); Err(Error::Default)
} }
} }
VarState::Unassigned(ref cs) => match cs {
Candidates::None => return Err(Error::Default),
Candidates::Value(v) => {
if min <= *v && *v <= max {
return Ok((*v, *v));
} else {
return Err(Error::Default);
}
}
Candidates::Set(_) => (),
},
VarState::Unified(_) => (),
}
if let VarState::Unified(other) = self.vars[idx] { VarState::Unassigned(ref mut cs) => match cs {
self.bound_candidate_range(other, min, max) Candidates::None => Err(Error::Default),
} else if let VarState::Unassigned(Candidates::Set(ref mut rc)) = self.vars[idx] { Candidates::Value(v) => {
if min <= *v && *v <= max {
Ok((*v, *v))
} else {
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_min = rc.iter().min().expect("candidates");
let &curr_max = rc.iter().max().expect("candidates"); let &curr_max = rc.iter().max().expect("candidates");
@ -697,8 +860,10 @@ impl<'a> PuzzleSearch<'a> {
} else { } else {
Ok((curr_min, curr_max)) Ok((curr_min, curr_max))
} }
} else { }
unreachable!(); },
VarState::Unified(other) => self.bound_candidate_range(other, min, max),
} }
} }

View File

@ -12,7 +12,7 @@ type Board = [[Val; SIZE]; SIZE];
fn make_sudoku(board: &Board) -> (Puzzle, Vec<Vec<VarToken>>) { fn make_sudoku(board: &Board) -> (Puzzle, Vec<Vec<VarToken>>) {
let mut sys = Puzzle::new(); 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 { for y in 0..SIZE {
sys.all_different(&vars[y]); sys.all_different(&vars[y]);

View File

@ -60,8 +60,8 @@ fn make_sums(size: usize) -> Vec<Val> {
let mut v = val as usize; let mut v = val as usize;
while v > 0 { while v > 0 {
count = count + (v & 1); count += (v & 1);
v = v >> 1; v >>= 1;
} }
if count == size / 2 { if count == size / 2 {