From 33684908d7e0979e904fd6dbcbf96442a28840b4 Mon Sep 17 00:00:00 2001 From: Hecht Date: Sat, 15 Oct 2022 23:43:40 +0200 Subject: [PATCH] Added CLI parser + reduced memory allocation --- Cargo.toml | 4 + src/hecht.rs | 65 ++++------- src/main.rs | 286 +++++++++++++++++++++------------------------- src/playground.rs | 23 +--- src/utils.rs | 109 +++++++++++++++--- 5 files changed, 254 insertions(+), 233 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 64ef6e2..8f46621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.0.15", features = ["derive"] } + +[dev-dependencies] +rstest = "0.15.0" diff --git a/src/hecht.rs b/src/hecht.rs index 905798d..8e3dbd4 100644 --- a/src/hecht.rs +++ b/src/hecht.rs @@ -1,16 +1,15 @@ -use std::collections::HashMap; - use crate::solver::Solver; use crate::playground::Playground; use crate::utils::Locatable; use crate::utils::LocatableVec; +use crate::utils::FsVec; pub struct HechtSolver { } -#[derive(Hash, Clone, Copy, Eq, PartialEq, Debug)] +#[derive(Hash, Clone, Copy, Eq, PartialEq, Debug, Default)] struct Point { pub x: usize, pub y: usize, @@ -32,8 +31,7 @@ struct Action { #[derive(Clone)] pub struct SolverPlayground { arr: [[u16; 9]; 9], - // todos : HashSet, - todos: LocatableVec, + todos: LocatableVec, valid: bool, rec_level: usize, } @@ -54,15 +52,13 @@ impl Solver for HechtSolver { impl SolverPlayground { pub fn new(pg: &Playground) -> SolverPlayground { - let mut points : LocatableVec = LocatableVec::new(); - for x in 0..9 { - for y in 0..9 { - points.put(Point{x, y, s: y / 3 * 3 + x / 3}); - } - } + let points : LocatableVec<_, 81> = [0usize..81].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(); let mut spg = SolverPlayground { arr: [[0x1FFu16; 9]; 9], todos: points.clone(), valid: true, rec_level: 0}; - + for p in points.iter() { if let Some(value) = pg.get_value(p.x, p.y) { spg.set_value(&p, value); @@ -75,15 +71,14 @@ impl SolverPlayground { fn to_playground(&self) -> Option { let mut pg = Playground::new(); - let bitmap = SolverPlayground::get_bitvalue_mapping(); - for x in 0..9 { for y in 0..9 { let value = self.arr[y][x]; - if value == 0 { + if value.count_ones() != 1 { return None } - pg.set_value(x, y, *bitmap.get(&value).unwrap_or(&0u8)); + + pg.set_value(x, y, (value.trailing_zeros() + 1) as u8); } } @@ -92,13 +87,10 @@ impl SolverPlayground { pub fn solve(&mut self) -> Option { while self.valid && self.todos.len() > 0 { - let actions = self.get_simple_action(); - if actions.len() == 0 { - return self.get_complex_action(); - } - - for action in actions { + if let Some(action) = self.get_simple_action() { self.apply(&action); + } else { + return self.get_complex_action(); } } @@ -118,19 +110,16 @@ impl SolverPlayground { self.arr[p.y][p.x] = bit_value; - let toprocess : Vec::<_> = self.todos.iter() + 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(); - - toprocess.iter().next(); + .collect::>() + .iter() + .for_each(|pos| self.remove_value(&pos, bit_value)); - for pos in toprocess { - self.remove_value(&pos, bit_value); - } } fn remove_value(&mut self, p : &Point, bit_value: u16) { @@ -170,27 +159,16 @@ impl SolverPlayground { 0b1_0000_0000u16]; // 9 } - #[inline] - fn get_bitvalue_mapping() -> HashMap { - let sequence = SolverPlayground::get_bitvalue_sequence(); - let mut result : HashMap = HashMap::with_capacity(sequence.len()); - - for (index, value) in sequence.iter().enumerate() { - result.insert(*value, (index + 1) as u8); - } - - result - } - #[inline] fn get_value(&self, p : &Point) -> u16 { return self.arr[p.y][p.x]; } - fn get_simple_action(&self) -> Vec { + fn get_simple_action(&self) -> Option { + // println!("Simple Action"); self.todos.iter() - .filter_map(|lhs| { + .find_map(|lhs| { let own_value = self.get_value(&lhs); self.todos.iter() .filter(|&rhs| lhs.x == rhs.x || lhs.y == rhs.y || lhs.s == rhs.s) @@ -210,7 +188,6 @@ impl SolverPlayground { .filter(|value| value.count_ones() == 1) .find_map(|value| SolverPlayground::create_action(lhs, value)) } ) - .collect() } fn get_complex_action(&self) -> Option { diff --git a/src/main.rs b/src/main.rs index 36531c6..9971d4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,155 +8,129 @@ mod hecht; use hecht::HechtSolver; use playground::Playground; use solver::Solver; +use clap::Parser; + +static FIELDS: [[u8;81];9] = + [ + [ + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0 + ], + [ + 7,0,6,3,0,8,0,0,9, + 0,0,0,2,6,0,3,0,0, + 0,5,0,0,0,0,0,8,0, + 0,0,0,9,0,1,8,0,2, + 4,0,0,0,0,0,0,0,6, + 9,0,3,8,0,6,0,0,0, + 0,9,0,0,0,0,0,7,0, + 0,0,5,0,2,4,0,0,0, + 6,0,0,1,0,3,2,0,4 + ], + [ + 0,0,4,1,0,0,3,0,0, + 0,7,0,0,0,8,0,0,1, + 0,0,0,0,0,0,7,0,9, + 8,0,3,0,5,9,0,0,2, + 0,0,9,6,0,1,5,0,0, + 1,0,0,8,2,0,9,0,4, + 6,0,1,0,0,0,0,0,0, + 4,0,0,3,0,0,0,9,0, + 0,0,8,0,0,4,2,0,0 + ], + [ + 0,0,0,0,2,3,4,0,8, + 0,0,0,0,7,4,6,9,0, + 0,0,0,8,9,0,0,0,7, + 0,0,9,3,0,0,7,4,0, + 0,7,0,0,4,0,0,3,0, + 0,8,3,0,0,7,2,0,0, + 5,0,0,0,1,9,0,0,0, + 0,9,2,4,3,0,0,0,0, + 3,0,6,7,5,0,0,0,0 + ], + [ + 0,0,0,0,6,0,0,3,0, + 0,9,6,0,0,5,0,0,4, + 0,0,0,7,0,1,0,6,0, + 0,1,0,0,0,3,0,9,5, + 0,4,9,0,1,0,7,2,0, + 7,3,0,5,0,0,0,1,0, + 0,6,0,9,0,7,0,0,0, + 4,0,0,8,0,0,9,5,0, + 0,2,0,0,5,0,0,0,0 + ], + [ + 9,0,6,1,0,7,3,0,4, + 0,8,3,0,0,5,0,0,7, + 0,0,0,0,0,0,2,0,9, + 8,4,5,3,7,1,9,2,6, + 3,0,2,5,6,8,0,0,1, + 1,0,7,9,2,4,0,3,0, + 6,0,9,4,3,2,7,0,8, + 0,3,0,0,1,6,5,0,0, + 2,1,8,7,5,0,0,0,3 + ], + [ + 0,8,0,0,0,0,0,0,0, + 0,0,0,6,3,0,0,4,1, + 0,0,0,5,0,0,0,0,0, + 0,0,0,0,0,0,0,9,0, + 0,0,0,0,0,0,0,5,0, + 9,0,0,0,0,0,0,0,0, + 0,0,0,8,0,2,0,0,0, + 0,5,0,0,9,0,0,0,0, + 0,0,0,4,5,0,9,0,0 + ], + [ + 0,0,0,0,0,7,0,2,0, + 0,0,4,0,0,0,0,3,0, + 0,1,0,0,9,0,0,0,5, + 0,0,0,0,0,6,3,0,0, + 6,0,0,0,0,0,1,7,0, + 0,7,3,4,0,0,0,0,2, + 0,0,0,0,6,0,0,9,0, + 3,0,7,0,8,0,5,0,0, + 0,5,1,0,0,0,0,0,0 + ], + [ + 0,0,0,0,0,0,0,1,0, + 0,3,0,0,0,4,0,0,0, + 0,0,0,0,0,0,3,9,0, + 0,4,0,8,0,2,0,0,0, + 5,0,0,0,0,0,0,0,0, + 0,0,9,0,6,0,2,5,0, + 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 + ] + ]; #[cfg(test)] mod tests { // Note this useful idiom: importing names from outer (for mod tests) scope. use super::*; - - #[test] - fn test_sudoku_1() { - let field = r#"7 0 6 3 0 8 0 0 9 - 0 0 0 2 6 0 3 0 0 - 0 5 0 0 0 0 0 8 0 - 0 0 0 9 0 1 8 0 2 - 4 0 0 0 0 0 0 0 6 - 9 0 3 8 0 6 0 0 0 - 0 9 0 0 0 0 0 7 0 - 0 0 5 0 2 4 0 0 0 - 6 0 0 1 0 3 2 0 4"#; - exec_test(field); - } - - #[test] - fn test_sudoku_2() { - let field = r#"{0,0,4,1,0,0,3,0,0}, - {0,7,0,0,0,8,0,0,1}, - {0,0,0,0,0,0,7,0,9}, - {8,0,3,0,5,9,0,0,2}, - {0,0,9,6,0,1,5,0,0}, - {1,0,0,8,2,0,9,0,4}, - {6,0,1,0,0,0,0,0,0}, - {4,0,0,3,0,0,0,9,0}, - {0,0,8,0,0,4,2,0,0}};"#; - - exec_test(field); - - } - - #[test] - fn test_sudoku_3() { - let field = r#"{0,0,0,0,2,3,4,0,8}, - {0,0,0,0,7,4,6,9,0}, - {0,0,0,8,9,0,0,0,7}, - {0,0,9,3,0,0,7,4,0}, - {0,7,0,0,4,0,0,3,0}, - {0,8,3,0,0,7,2,0,0}, - {5,0,0,0,1,9,0,0,0}, - {0,9,2,4,3,0,0,0,0}, - {3,0,6,7,5,0,0,0,0}}"#; - - exec_test(field); - - } - - #[test] - fn test_sudoku_4() { - let field = r#"{0,0,0,0,6,0,0,3,0}, - {0,9,6,0,0,5,0,0,4}, - {0,0,0,7,0,1,0,6,0}, - {0,1,0,0,0,3,0,9,5}, - {0,4,9,0,1,0,7,2,0}, - {7,3,0,5,0,0,0,1,0}, - {0,6,0,9,0,7,0,0,0}, - {4,0,0,8,0,0,9,5,0}, - {0,2,0,0,5,0,0,0,0}}"#; - - exec_test(field); - } - - #[test] - fn test_sudoku_5() { - let field = r#"{9,0,6,1,0,7,3,0,4}, - {0,8,3,0,0,5,0,0,7}, - {0,0,0,0,0,0,2,0,9}, - {8,4,5,3,7,1,9,2,6}, - {3,0,2,5,6,8,0,0,1}, - {1,0,7,9,2,4,0,3,0}, - {6,0,9,4,3,2,7,0,8}, - {0,3,0,0,1,6,5,0,0}, - {2,1,8,7,5,0,0,0,3}}"#; - - exec_test(field); - } - - #[test] - fn test_sudoku_6() { - let field = r#"{0,8,0,0,0,0,0,0,0}, - {0,0,0,6,3,0,0,4,1}, - {0,0,0,5,0,0,0,0,0}, - {0,0,0,0,0,0,0,9,0}, - {0,0,0,0,0,0,0,5,0}, - {9,0,0,0,0,0,0,0,0}, - {0,0,0,8,0,2,0,0,0}, - {0,5,0,0,9,0,0,0,0}, - {0,0,0,4,5,0,9,0,0}}"#; - - exec_test(field); - } - - #[test] - fn test_sudoku_7() { - let field = r#"{0,0,0,0,0,7,0,2,0}, - {0,0,4,0,0,0,0,3,0}, - {0,1,0,0,9,0,0,0,5}, - {0,0,0,0,0,6,3,0,0}, - {6,0,0,0,0,0,1,7,0}, - {0,7,3,4,0,0,0,0,2}, - {0,0,0,0,6,0,0,9,0}, - {3,0,7,0,8,0,5,0,0}, - {0,5,1,0,0,0,0,0,0}}"#; - - exec_test(field); - } - - #[test] - fn test_sudoku_8() { - let field = r#"{0,0,0,0,0,0,0,1,0}, - {0,3,0,0,0,4,0,0,0}, - {0,0,0,0,0,0,3,9,0}, - {0,4,0,8,0,2,0,0,0}, - {5,0,0,0,0,0,0,0,0}, - {0,0,9,0,6,0,2,5,0}, - {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}}"#; - - exec_test(field); - } - - /** - * Tests if the solver can cope with - * an empty board - */ - #[test] - fn test_sudoku_empty() { - let field = r#"{0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}, - {0,0,0,0,0,0,0,0,0}}"#; - - exec_test(field); - } - - - fn exec_test(field : &str) { - let pg = Playground::from(field); + 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) { + let pg = Playground::from_array(&FIELDS[field]); let solver = HechtSolver::new(); let result = solver.solve(&pg); @@ -169,19 +143,21 @@ mod tests { } } -fn main() { - let field = r#"{0,0,4,1,0,0,3,0,0}, - {0,7,0,0,0,8,0,0,1}, - {0,0,0,0,0,0,7,0,9}, - {8,0,3,0,5,9,0,0,2}, - {0,0,9,6,0,1,5,0,0}, - {1,0,0,8,2,0,9,0,4}, - {6,0,1,0,0,0,0,0,0}, - {4,0,0,3,0,0,0,9,0}, - {0,0,8,0,0,4,2,0,0}};"#; +/// 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, +} + +fn main() { + + let args = Args::parse(); - let pg = Playground::from(field); + let pg = Playground::from_array(&FIELDS[args.scenario as usize]); pg.print(); diff --git a/src/playground.rs b/src/playground.rs index 2f69f49..54963af 100644 --- a/src/playground.rs +++ b/src/playground.rs @@ -8,28 +8,17 @@ pub struct Playground { impl Playground { - pub fn init(&mut self, data: String) { - let mut idx = 0; - for c in data.chars() { - idx = match c { - '1'..='9' => { - let value = c.to_digit(10).unwrap() as u8; - self.arr[idx / 9][idx % 9] = value; - idx + 1 - }, - '0' =>idx + 1, - _ => idx, - } - } - } - pub fn new() -> Playground { return Playground { arr: [[0; 9]; 9] }; } - pub fn from(s: &str) -> Playground { + pub fn from_array(arr: &[u8;81]) -> Playground { let mut p = Playground::new(); - p.init(String::from(s)); + for y in 0..9 { + for x in 0..9 { + p.arr[y][x] = arr[y*9+x]; + } + } p } diff --git a/src/utils.rs b/src/utils.rs index e2de6f4..df59b7b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,30 +4,26 @@ pub trait Locatable { fn get_xy(&self) -> (usize, usize); } -pub trait LocatableFactory { - fn create(x:usize, y:usize) -> T; -} - #[derive(Clone)] -pub struct LocatableVec { - arr: [Option; 81], +pub struct LocatableVec { + arr: [Option; COUNT], len: usize, } -pub struct LocatableVecIter<'a, T:Locatable + std::marker::Copy> { - vec : &'a LocatableVec, +pub struct LocatableVecIter<'a, T:Locatable + Copy, const COUNT: usize> { + vec : &'a LocatableVec, idx: usize, -} // (&'a LocatableVec, usize); - + cnt: usize, +} -impl LocatableVec { +impl LocatableVec { - pub fn new() -> LocatableVec { - LocatableVec{arr: [None; 81], len: 0} + pub fn new() -> LocatableVec { + LocatableVec{arr: [None; COUNT], len: 0} } - pub fn iter(&self) -> LocatableVecIter<'_, T> { - LocatableVecIter{vec: self, idx: 0} + pub fn iter(&self) -> LocatableVecIter<'_, T, COUNT> { + LocatableVecIter{vec: self, idx: 0, cnt: 0} } pub fn len(&self) -> usize { @@ -56,7 +52,7 @@ impl LocatableVec { } -impl<'a, T:Locatable + std::marker::Copy> Iterator for LocatableVecIter<'a, T> { +impl<'a, T:Locatable + Copy, const COUNT: usize> Iterator for LocatableVecIter<'a, T, COUNT> { type Item = &'a T; fn next(&mut self) -> Option { @@ -64,9 +60,88 @@ impl<'a, T:Locatable + std::marker::Copy> Iterator for LocatableVecIter<'a, T> { let pos = &self.vec.arr[i]; if pos.is_some() { self.idx = i + 1; + self.cnt += 1; return pos.as_ref(); } } None } -} \ No newline at end of file + + fn size_hint(&self) -> (usize, Option) { + (self.vec.len - self.cnt, Some(self.vec.arr.len() - self.idx)) + } +} + +impl<'a, L:Locatable + Copy, const COUNT: usize> FromIterator for LocatableVec { + fn from_iter>(iter: T) -> Self { + + let mut x = LocatableVec{arr: [None; COUNT], len: 0}; + for p in iter { + x.put(p); + } + x + } + +} + +pub struct FsVec { + arr: [T; COUNT], + len: usize +} + +pub struct FsVecIter<'a, T:Copy + Sized + Default, const COUNT: usize> { + vec : &'a FsVec, + idx: usize, +} + + +impl FsVec { + pub fn new() -> FsVec { + FsVec{arr: [Default::default(); COUNT], len: 0} + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn append(&mut self, value : T) { + self.arr[self.len] = value; + self.len += 1; + } + + pub fn iter(&self) -> FsVecIter<'_, T, COUNT> { + FsVecIter{vec: self, idx: 0} + } + +} + +impl<'a, T:Sized + Copy + Default, const COUNT: usize> Iterator for FsVecIter<'a, T, COUNT> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.idx >= self.vec.len { + return None + } + let result = Some(&self.vec.arr[self.idx]); + self.idx += 1; + result + } + + fn size_hint(&self) -> (usize, Option) { + (self.vec.len() - self.idx, Some(self.vec.len() - self.idx)) + } +} + +impl<'a, L:Sized + Copy + Default, const COUNT: usize> FromIterator for FsVec { + fn from_iter>(iter: T) -> Self { + + let mut x = FsVec::new(); + for elem in iter { + x.append(elem); + } + x + } + +} + +