Applied some fixes

master
Hecht 2 years ago
parent 13643c2df3
commit a47cf2e7e4

@ -1,7 +1,7 @@
use std::collections::HashSet;
#[derive(Clone)]
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct Board {
arr: [[u8; 9]; 9]
}

@ -1,44 +1,53 @@
use std::collections::HashSet;
use crate::solver::Solver;
use crate::solver::{Solver, Generator, SodokuComplexity};
use crate::board::Board;
use crate::utils::*;
use std::hash::{Hash, Hasher};
use std::ops::ControlFlow;
static CELLS : usize = 81;
const NUM : usize = 3;
const VALUES : usize = NUM * NUM;
const CELLS : usize = VALUES * VALUES;
type TodoType<T> = IndexableVec<T, CELLS>;
// type TodoType<T> = HashSet<T>;
pub struct HechtSolver {
}
#[derive(Hash, Clone, Eq, PartialEq, Debug)]
#[derive(Hash, Clone, Eq, PartialEq, Debug, Copy)]
struct Point {
pub x: usize,
pub y: usize,
pub s: usize,
}
#[derive(Debug)]
struct Action {
pub p : Point,
pub value: u16
#[derive(Debug, Clone)]
enum Action {
Trivial(Point, u16),
Logic(Point, u16),
Probe(Point, u16),
}
#[derive(Clone, Eq)]
#[derive(Clone)]
struct SolverBoard {
arr: [[u16; 9]; 9],
todos: HashSet<Point>,
arr: [[u16; VALUES]; VALUES],
todos: TodoType<Point>,
valid: bool,
audit: Vec<Action>,
}
enum SolverBoardItem {
Recursive(SolverBoard, Vec<Action>),
Initial(SolverBoard),
Recursive(Vec<Action>),
Initial,
}
struct SolverBoardIterator {
cache: HashSet<[[u16; 9]; 9]>, // to avoid processing the same board twice
cache: HashSet<[[u16; VALUES]; VALUES]>, // to avoid processing the same board twice
board: SolverBoard,
stack: Vec<SolverBoardItem>,
}
@ -58,52 +67,79 @@ impl HechtSolver {
pub fn new() -> HechtSolver {
return HechtSolver {};
}
pub fn test(&self, board: &Board) -> bool {
let mut invalid_counter = 0;
let mut unsolved_counter = 0;
let mut solved_counter = 0;
SolverBoardIterator::new(board)
.inspect(|_| invalid_counter += 1)
.filter(|x| x.valid)
.inspect(|_| unsolved_counter += 1)
.filter(|x| x.is_solved())
.inspect(|_| solved_counter += 1)
.inspect(|x| x.to_board().unwrap().print() )
.nth(1024).and_then(|_| {println!("I: {}, U: {}, S: {}", invalid_counter, unsolved_counter, solved_counter); Some(true)})
.is_some()
}
}
impl Solver for HechtSolver {
fn solve(&self, pg: &Board) -> Option<Board> {
SolverBoardIterator::new(pg)
fn solve(&self, board: &Board) -> Option<Board> {
SolverBoardIterator::new(board)
.find(|x| x.is_solved())
.map(|x| x.to_board())
.flatten()
}
fn is_unique(&self, pg: &Board) -> bool {
let first_solution = SolverBoard::new();
SolverBoardIterator::new(pg)
fn is_unique(&self, board: &Board) -> bool {
SolverBoardIterator::new(board)
.filter(|x| x.is_solved())
.try_fold(first_solution, |prev, x| {
if !prev.is_solved() || prev == x {
ControlFlow::Continue(x)
} else {
ControlFlow::Break(prev)
}
}).is_continue()
.nth(1).is_none()
}
}
impl Generator for HechtSolver {
fn generate(&self, _complexity:SodokuComplexity) -> Board {
let board = Board::new();
SolverBoardIterator::new(&board)
.filter_map(|x| x.to_board())
.filter(|board| self.is_unique(board))
.next()
.unwrap_or(board)
}
}
impl SolverBoard {
pub fn new() -> SolverBoard {
let points = [0usize..CELLS].into_iter().flatten()
.map(|idx| [idx%9, idx/9])
.map(|p| Point{x: p[0], y:p[1], s: p[1] / 3 * 3 + p[0] / 3})
.collect::<HashSet<_>>();
.map(|idx| Point::from_index(idx))
.collect::<TodoType<_>>();
SolverBoard { arr: [[0x1FFu16; 9]; 9], todos: points, valid: true}
SolverBoard { arr: [[0x1FFu16; VALUES]; VALUES], todos: points, valid: true, audit: Vec::new()}
}
pub fn from(pg: &Board) -> SolverBoard {
let mut spg = SolverBoard::new();
for p in spg.todos.clone().iter() {
for idx in 0usize..CELLS {
let p = Point::from_index(idx);
if let Some(value) = pg.get_value(p.x, p.y) {
spg.set_value(&p, value);
assert!(spg.valid);
}
}
spg.solve_logically();
assert!(spg.valid);
spg
}
@ -111,8 +147,8 @@ impl SolverBoard {
fn to_board(&self) -> Option<Board> {
let mut pg = Board::new();
for x in 0..9 {
for y in 0..9 {
for x in 0..VALUES {
for y in 0..VALUES {
let value = self.arr[y][x];
if value.count_ones() != 1 {
return None
@ -128,7 +164,7 @@ impl SolverBoard {
fn solve_logically(&mut self) {
while self.valid {
if let Some(action) = self.get_simple_action() {
self.set_bit_value(&action.p, action.value);
self.apply(&action);
} else {
break;
}
@ -144,7 +180,33 @@ impl SolverBoard {
self.set_bit_value(p, bit_value)
}
fn get_sector(index : usize) -> usize {
static SECTOR_ARRAY : [usize; CELLS] = [
0,0,0,1,1,1,2,2,2,
0,0,0,1,1,1,2,2,2,
0,0,0,1,1,1,2,2,2,
3,3,3,4,4,4,5,5,5,
3,3,3,4,4,4,5,5,5,
3,3,3,4,4,4,5,5,5,
6,6,6,7,7,7,8,8,8,
6,6,6,7,7,7,8,8,8,
6,6,6,7,7,7,8,8,8,
];
SECTOR_ARRAY[index]
}
#[inline]
fn get_y(index : usize) -> usize {
index / VALUES
}
#[inline]
fn get_x(index : usize) -> usize {
index % VALUES
}
fn set_bit_value(&mut self, p : &Point, bit_value : u16) {
assert!(bit_value.count_ones() == 1);
if !self.todos.remove(p) {
// Point was not in todo-list -> no further action required
return;
@ -152,28 +214,36 @@ impl SolverBoard {
self.arr[p.y][p.x] = bit_value;
self.todos.iter()
// Filter positions that do not have the right coordinates
.filter(|&pos| pos.x == p.x || pos.y == p.y || pos.s == p.s)
// Filter positions that were known not to hold the value
.filter(|&pos| (self.arr[pos.y][pos.x] & bit_value) != 0)
.cloned()
.collect::<Vec::<_>>()
.iter()
.for_each(|pos| self.remove_value(&pos, bit_value));
// self.todos.iter()
// // Filter positions that do not have the right coordinates
// .filter(|&pos| pos.x == p.x || pos.y == p.y || pos.s == p.s)
// // Filter positions that were known not to hold the value
// .filter(|&pos| (self.arr[pos.y][pos.x] & bit_value) != 0)
// .cloned()
// .collect::<Vec::<_>>()
// .iter()
// .for_each(|pos| self.remove_value(&pos, bit_value));
for idx in 0..CELLS {
let other = Point::from_index(idx);
if p != &other && (other.x == p.x || other.y == p.y || other.s == p.s) {
self.remove_value(other, bit_value)
}
}
}
fn remove_value(&mut self, p : &Point, bit_value: u16) {
if (self.get_value(p) & bit_value) == 0 {
fn remove_value(&mut self, p : Point, bit_value: u16) {
if (self.arr[p.y][p.x] & bit_value) == 0 {
return;
}
self.arr[p.y][p.x] &= !bit_value;
let point_value = self.arr[p.y][p.x];
if let Some(action) = SolverBoard::create_action(p, self.arr[p.y][p.x]) {
self.set_bit_value(&action.p, action.value);
} else if self.arr[p.y][p.x] == 0 {
self.valid = false;
match point_value.count_ones() {
1 => self.apply(&Action::Trivial(p, point_value)),
0 => self.valid = false,
_ => {}
}
}
@ -181,16 +251,23 @@ impl SolverBoard {
if bit_value.count_ones() != 1 {
return None;
}
Some(Action{p : p.clone(), value : bit_value})
Some(Action::Logic(p.clone(), bit_value))
}
fn apply(&mut self, action : &Action) {
self.set_bit_value(&action.p, action.value);
self.solve_logically();
let (point, value) = action.get();
self.set_bit_value(point, value);
if let Action::Probe(_,_) = action {
self.solve_logically();
}
self.audit.push(action.clone());
}
#[inline]
fn get_bitvalue_sequence() -> &'static[u16; 9] {
fn get_bitvalue_sequence() -> &'static[u16; VALUES] {
return &[0b0_0000_0001u16, // 1
0b0_0000_0010u16, // 2
0b0_0000_0100u16, // 3
@ -236,16 +313,16 @@ impl SolverBoard {
fn get_all_actions(&self) -> Vec<Action> {
if self.valid && !self.is_solved() {
let mut vec = self.todos.iter()
.map(|point| Action{p: point.clone(), value: self.get_value(point)})
.map(|point| (point, self.get_value(point)))
.collect::<Vec<_>>(); // collect into the vector so we can (optionally) apply sorting
// now sort it (lowest value should be at the end (we use it as a stack))
vec.sort_unstable_by_key(|action| CELLS as u32 - action.value.count_ones());
vec.sort_unstable_by_key(|action| CELLS as u32 - action.1.count_ones());
return vec.iter()
.map(|action| SolverBoard::get_bitvalue_sequence().iter()
.filter(|&value| action.value & value != 0)
.map(|&value| Action{p: action.p.clone(), value})
.filter(|&value| (action.1 & value) != 0)
.map(|&value| Action::Probe(action.0.clone(), value))
.collect::<Vec<_>>()
)
.flatten()
@ -262,70 +339,96 @@ impl SolverBoardIterator {
}
fn from_board(board: SolverBoard) -> SolverBoardIterator {
SolverBoardIterator{cache: HashSet::with_capacity(2048), stack: vec![SolverBoardItem::Initial(board)]}
SolverBoardIterator{cache: HashSet::with_capacity(128), board, stack: vec![SolverBoardItem::Initial]}
}
fn next_board(&mut self) -> Option<SolverBoard> {
let mut result : Option<SolverBoard> = None;
while let Some(mut item) = self.stack.pop() {
if let Some(action) = item.pop_action() {
if let Some(board) = item.get_board() {
let mut new_board = board.clone();
self.stack.push(item);
new_board.apply(&action);
result = self.add_recursive(new_board);
}
} else if let Some(board) = item.get_board() { // initial
result = self.add_recursive(board.clone());
let mut result;
while let Some(item) = self.stack.pop() {
if let SolverBoardItem::Recursive(actions) = item {
result = self.push_actions(&actions);
} else {
// initial
result = self.push_actions(&Vec::new());
}
if result.is_some() {
return result
return result;
}
}
println!("Cache size: {}", self.cache.len());
None
}
fn add_to_cache(&mut self, board: &SolverBoard) -> Option<SolverBoard> {
if !self.cache.contains(&board.arr) {
self.cache.insert(board.arr);
return Some(board.clone())
fn push_actions(&mut self, actions: &Vec<Action>) -> Option<SolverBoard> {
if self.already_processed(actions) {
println!("Early hit! Stack: {}, Cache: {}", self.stack.len(), self.cache.len());
return None;
}
None
}
fn add_recursive(&mut self, board: SolverBoard) -> Option<SolverBoard> {
if board.valid {
let result = self.add_to_cache(&board);
if result.is_some() {
let actions = board.get_all_actions();
self.stack.push(SolverBoardItem::Recursive(board, actions));
let mut new_board = self.board.clone();
let mut iter = actions.iter().peekable();
let mut arr_backup : Option<[[u16; VALUES]; VALUES]>= None;
while let Some(action) = iter.next() {
if iter.peek().is_some() {
new_board.apply(&action);
} else {
arr_backup = Some(new_board.arr);
new_board.apply(&action);
}
return result;
}
if !new_board.valid {
if let Some(mut arr) = arr_backup {
if let Some(action) = actions.last() {
let (point, value) = action.get();
arr[point.y][point.x] = value;
self.insert_to_cache(arr);
}
}
return None
}
if self.insert_to_cache(new_board.arr) {
if !new_board.is_solved() {
let mut actions_copy = actions.to_vec();
for action in new_board.get_all_actions() {
actions_copy.push(action);
self.stack.push(SolverBoardItem::Recursive(actions_copy.to_vec()));
actions_copy.pop();
}
}
return Some(new_board);
} else {
println!("Late hit! Stack: {}, Cache: {}", self.stack.len(), self.cache.len())
}
None
}
}
impl SolverBoardItem {
fn get_board(&self) -> Option<&SolverBoard> {
if let SolverBoardItem::Recursive(board, _) = self {
return Some(board)
} else if let SolverBoardItem::Initial(board) = self {
return Some(board)
/**
* Check if we can find a board in the cache, where all actions in the
* vector have been applied.
*/
fn already_processed(&mut self, actions : &Vec<Action>) -> bool {
for board in self.cache.iter() {
let applied = actions.iter()
.map(|a| a.get())
.all(|a| board[a.0.y][a.0.x] == a.1);
if applied {
return true
}
}
None
false
}
fn pop_action(&mut self) -> Option<Action> {
if let SolverBoardItem::Recursive(_, actions) = self {
return actions.pop()
fn insert_to_cache(&mut self, mut array : [[u16; VALUES]; VALUES]) -> bool {
for x in array.iter_mut()
.flatten()
.filter(|value| value.count_ones() != 1) {
*x = 0;
}
None
self.cache.insert(array)
}
}
@ -335,5 +438,29 @@ impl Iterator for SolverBoardIterator {
fn next(&mut self) -> Option<Self::Item> {
self.next_board()
}
}
}
impl Action {
fn get(&self) -> (&Point, u16) {
match self {
Action::Logic(point,value) | Action::Probe(point,value) | Action::Trivial(point,value) => return (point, *value)
}
}
}
impl Indexable for Point {
fn to_index(&self) -> usize {
self.y * VALUES + self.x
}
}
impl Point {
fn from_xy(x: usize, y: usize) -> Point {
Point{x, y, s: (y / NUM) * NUM + x / NUM}
}
fn from_index(idx: usize) -> Point {
Point{x: SolverBoard::get_x(idx), y: SolverBoard::get_y(idx), s: SolverBoard::get_sector(idx)}
}
}

@ -3,13 +3,14 @@ mod board;
mod solver;
mod mysolver;
mod hecht;
mod utils;
use hecht::HechtSolver;
use board::Board;
use solver::Solver;
use clap::Parser;
use solver::{Solver, Generator, SodokuComplexity};
use clap::{Parser, Subcommand};
static FIELDS: [[u8;81];9] =
static FIELDS: [[u8;81];10] =
[
[
0,0,0,0,0,0,0,0,0,
@ -109,6 +110,17 @@ static FIELDS: [[u8;81];9] =
8,0,0,0,3,0,9,0,0,
9,0,0,4,0,0,0,8,0,
1,0,0,6,0,9,0,0,5
],
[ // Cannot be solved!
1,7,3,0,0,0,0,4,0,
0,0,0,0,0,0,9,0,5,
0,5,9,6,7,0,0,0,0,
0,2,0,0,8,0,0,0,7,
0,0,1,0,0,0,8,2,6,
7,0,0,0,0,0,0,0,1,
0,0,0,1,0,3,0,0,0,
0,0,0,2,5,0,0,0,0,
0,4,0,0,0,0,0,8,3
]
];
@ -119,37 +131,84 @@ mod tests {
use rstest::*;
#[rstest]
#[case(0)]
#[case(1)]
#[case(2)]
#[case(3)]
#[case(4)]
#[case(5)]
#[case(6)]
#[case(7)]
#[case(8)]
fn solve_sudoku(#[case] field : usize) {
#[case(0, true)]
#[case(1, true)]
#[case(2, true)]
#[case(3, true)]
#[case(4, true)]
#[case(5, true)]
#[case(6, true)]
#[case(7, true)]
#[case(8, true)]
#[case(9, false)]
fn solve_sudoku(#[case] field : usize, #[case] valid : bool) {
let pg = Board::from_array(&FIELDS[field]);
let solver = HechtSolver::new();
let result = solver.solve(&pg);
assert!(result.is_some());
let result_pg = result.unwrap();
assert!(result_pg.is_valid());
assert!(result_pg.is_solved());
assert!(result.is_some() == valid);
if valid {
let result_pg = result.unwrap();
assert!(result_pg.is_valid());
assert!(result_pg.is_solved());
assert!(result_pg.contains(&pg));
}
}
#[rstest]
#[case(0, true)]
#[case(1, false)]
#[case(2, false)]
#[case(3, false)]
#[case(4, false)]
#[case(5, false)]
#[case(6, true)]
#[case(7, false)]
#[case(8, false)]
fn uniqueness_test(#[case] field : usize, #[case] multi : bool) {
let pg = Board::from_array(&FIELDS[field]);
let solver = HechtSolver::new();
let result = solver.is_unique(&pg);
assert!(result_pg.contains(&pg));
assert!(result != multi)
}
#[test]
fn generator_test_creates_valid_boards() {
let solver = HechtSolver::new();
let board = solver.generate(SodokuComplexity::Hard);
assert!(board.is_valid());
}
#[test]
fn generator_test_creates_not_solved_boards() {
let solver = HechtSolver::new();
let board = solver.generate(SodokuComplexity::Hard);
assert!(!board.is_solved());
}
}
#[derive(Subcommand, Debug)]
enum Commands {
// Simply let the solver solve the sudoku
Solve,
// Let the solver determine if there are multiple solutions
Unique,
// Instead of solving ... generate a sudoku
Generate,
}
/// Sudoku solver program
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Sudoku index number to use
#[arg(short, long, default_value_t = 1,value_parser = clap::value_parser!(u8).range(0..FIELDS.len() as i64))]
scenario: u8,
/// Sudoku index number to use
#[arg(short, long, default_value_t = 1,value_parser = clap::value_parser!(u8).range(0..FIELDS.len() as i64))]
scenario: u8,
#[command(subcommand)]
command: Option<Commands>,
}
fn main() {
@ -162,27 +221,35 @@ fn main() {
let solver = HechtSolver::new();
println!("");
if let Some(result) = solver.solve(&pg) {
if !result.contains(&pg) {
println!("Solver modified predefined fields!")
} if !result.is_valid() {
println!("Solver has not correctly solved the sudoku!")
} else if !result.is_solved() {
println!("Solver was not able to solve the sudoku!")
} else {
println!("Solver found a solution for this sudoku!!")
// solver.test(&pg);
match &args.command {
Some(Commands::Unique) => {
if solver.is_unique(&pg) {
println!("Solver states, that this sudoku has only one solution!")
} else {
println!("Solver states, that this sudoku has multiple possible solutions!");
}
}
result.print();
if solver.is_unique(&pg) {
println!("Solver states, that this sudoku has only one solution!")
} else {
println!("Solver states, that this sudoku has multiple solutions!")
Some(Commands::Generate) => {
let board = solver.generate(SodokuComplexity::Hard);
board.print();
}
_ => {
if let Some(result) = solver.solve(&pg) {
if !result.contains(&pg) {
println!("Solver modified predefined fields!");
} if !result.is_valid() {
println!("Solver has not correctly solved the sudoku!");
} else if !result.is_solved() {
println!("Solver was not able to solve the sudoku!");
} else {
println!("Solver found a solution for this sudoku!!");
result.print();
}
} else {
println!("Solver was not able to solve the sodoku!");
}
}
} else {
println!("Solver was not able to solve the sodoku!")
}
}

@ -20,4 +20,5 @@ impl Solver for MySolver {
// FIXME: Implement
false
}
}

@ -2,8 +2,17 @@
use crate::board::Board;
pub trait Solver {
/**
* Finds one solution for the board.
*/
fn solve(&self, _: &Board) -> Option<Board>;
/**
* Calculates if there are multiple solutions for the board.
*/
fn is_unique(&self, _ : &Board) -> bool;
}
pub enum SodokuComplexity {
@ -16,5 +25,5 @@ pub enum SodokuComplexity {
}
pub trait Generator{
fn generate(&self, _complexity:&SodokuComplexity) -> Board;
fn generate(&self, _complexity:SodokuComplexity) -> Board;
}

@ -1,29 +1,29 @@
pub trait Locatable {
fn get_xy(&self) -> (usize, usize);
pub trait Indexable {
fn to_index(&self) -> usize;
}
#[derive(Clone)]
pub struct LocatableVec<T:Locatable + Copy, const COUNT: usize> {
pub struct IndexableVec<T:Indexable + Copy, const COUNT: usize> {
arr: [Option<T>; COUNT],
len: usize,
}
pub struct LocatableVecIter<'a, T:Locatable + Copy, const COUNT: usize> {
vec : &'a LocatableVec<T, COUNT>,
pub struct IndexableVecIter<'a, T:Indexable + Copy, const COUNT: usize> {
vec : &'a IndexableVec<T, COUNT>,
idx: usize,
cnt: usize,
}
impl<T:Locatable + Copy, const COUNT: usize> LocatableVec<T, COUNT> {
impl<T:Indexable + Copy, const COUNT: usize> IndexableVec<T, COUNT> {
pub fn new() -> LocatableVec<T, COUNT> {
LocatableVec{arr: [None; COUNT], len: 0}
pub fn new() -> IndexableVec<T, COUNT> {
IndexableVec{arr: [None; COUNT], len: 0}
}
pub fn iter(&self) -> LocatableVecIter<'_, T, COUNT> {
LocatableVecIter{vec: self, idx: 0, cnt: 0}
pub fn iter(&self) -> IndexableVecIter<'_, T, COUNT> {
IndexableVecIter{vec: self, idx: 0, cnt: 0}
}
pub fn len(&self) -> usize {
@ -31,8 +31,7 @@ impl<T:Locatable + Copy, const COUNT: usize> LocatableVec<T, COUNT> {
}
pub fn put(&mut self, value : T) {
let (x,y) = value.get_xy();
let idx = y * 9 + x;
let idx = value.to_index();
if self.arr[idx].is_none() {
self.len += 1;
}
@ -40,8 +39,7 @@ impl<T:Locatable + Copy, const COUNT: usize> LocatableVec<T, COUNT> {
}
pub fn remove(&mut self, value : &T) -> bool{
let (x,y) = value.get_xy();
let idx = y * 9 + x;
let idx = value.to_index();
if self.arr[idx].is_some() {
self.arr[idx] = None;
self.len -= 1;
@ -52,7 +50,7 @@ impl<T:Locatable + Copy, const COUNT: usize> LocatableVec<T, COUNT> {
}
impl<'a, T:Locatable + Copy, const COUNT: usize> Iterator for LocatableVecIter<'a, T, COUNT> {
impl<'a, T:Indexable + Copy, const COUNT: usize> Iterator for IndexableVecIter<'a, T, COUNT> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
@ -72,10 +70,10 @@ impl<'a, T:Locatable + Copy, const COUNT: usize> Iterator for LocatableVecIter<'
}
}
impl<'a, L:Locatable + Copy, const COUNT: usize> FromIterator<L> for LocatableVec<L, COUNT> {
impl<'a, L:Indexable + Copy, const COUNT: usize> FromIterator<L> for IndexableVec<L, COUNT> {
fn from_iter<T: IntoIterator<Item = L>>(iter: T) -> Self {
let mut x = LocatableVec{arr: [None; COUNT], len: 0};
let mut x = IndexableVec{arr: [None; COUNT], len: 0};
for p in iter {
x.put(p);
}

Loading…
Cancel
Save