diff --git a/src/board.rs b/src/board.rs index 97aff23..99ef939 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; -#[derive(Clone)] +#[derive(Clone, Hash, PartialEq, Eq)] pub struct Board { arr: [[u8; 9]; 9] } diff --git a/src/hecht.rs b/src/hecht.rs index cf74eab..69a7ac7 100644 --- a/src/hecht.rs +++ b/src/hecht.rs @@ -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 = IndexableVec; +// type TodoType = HashSet; + 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, + arr: [[u16; VALUES]; VALUES], + todos: TodoType, valid: bool, + audit: Vec, } enum SolverBoardItem { - Recursive(SolverBoard, Vec), - Initial(SolverBoard), + Recursive(Vec), + 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, } @@ -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 { - SolverBoardIterator::new(pg) + fn solve(&self, board: &Board) -> Option { + 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::>(); + .map(|idx| Point::from_index(idx)) + .collect::>(); - 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 { 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::>() - .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::>() +// .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 { 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::>(); // 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::>() ) .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 { - let mut result : Option = 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 { - if !self.cache.contains(&board.arr) { - self.cache.insert(board.arr); - return Some(board.clone()) + fn push_actions(&mut self, actions: &Vec) -> Option { + 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 { - 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) -> 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 { - 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.next_board() } - -} \ No newline at end of file +} + +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)} + } +} + diff --git a/src/main.rs b/src/main.rs index c6da2f9..70ae4bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, } 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!") } } diff --git a/src/mysolver.rs b/src/mysolver.rs index f9b9f81..a39efb8 100644 --- a/src/mysolver.rs +++ b/src/mysolver.rs @@ -20,4 +20,5 @@ impl Solver for MySolver { // FIXME: Implement false } + } \ No newline at end of file diff --git a/src/solver.rs b/src/solver.rs index 67dbd21..61e8157 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -2,8 +2,17 @@ use crate::board::Board; pub trait Solver { + + /** + * Finds one solution for the board. + */ fn solve(&self, _: &Board) -> Option; + + /** + * 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; } \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index df59b7b..7bf645d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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 { +pub struct IndexableVec { arr: [Option; COUNT], len: usize, } -pub struct LocatableVecIter<'a, T:Locatable + Copy, const COUNT: usize> { - vec : &'a LocatableVec, +pub struct IndexableVecIter<'a, T:Indexable + Copy, const COUNT: usize> { + vec : &'a IndexableVec, idx: usize, cnt: usize, } -impl LocatableVec { +impl IndexableVec { - pub fn new() -> LocatableVec { - LocatableVec{arr: [None; COUNT], len: 0} + pub fn new() -> IndexableVec { + 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 LocatableVec { } 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 LocatableVec { } 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 LocatableVec { } -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 { @@ -72,10 +70,10 @@ impl<'a, T:Locatable + Copy, const COUNT: usize> Iterator for LocatableVecIter<' } } -impl<'a, L:Locatable + Copy, const COUNT: usize> FromIterator for LocatableVec { +impl<'a, L:Indexable + Copy, const COUNT: usize> FromIterator for IndexableVec { fn from_iter>(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); }