From 830cc81bc92dedd1675da86fd202b4eea9beb349 Mon Sep 17 00:00:00 2001 From: Hecht Date: Mon, 10 Oct 2022 22:26:13 +0200 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.toml | 8 ++ README.md | 30 ++++++- src/main.rs | 208 ++++++++++++++++++++++++++++++++++++++++++++++ src/mysolver.rs | 18 ++++ src/playground.rs | 134 +++++++++++++++++++++++++++++ src/solver.rs | 6 ++ 7 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/mysolver.rs create mode 100644 src/playground.rs create mode 100644 src/solver.rs diff --git a/.gitignore b/.gitignore index 562218f..7b6c977 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ local.properties .settings/ .loadpath .recommenders +.project # External tool builders .externalToolBuilders/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..64ef6e2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "sudoku-rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/README.md b/README.md index d38eafe..8b8834c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ # sudoku-solver-rust -Just a small project to learn rust. \ No newline at end of file +Just a small project to learn rust. + +Update the `mysolver.rs` file with your solver implementation. +Some unit tests for corner case testing have been added. + +```rust +use crate::solver::Solver; +use crate::playground::Playground; + +pub struct MySolver { +} + +impl MySolver { + pub fn new() -> MySolver { + return MySolver {}; + } +} + +impl Solver for MySolver { + fn solve(&self, pg: &Playground) -> Option { + // FIXME: Implement! + None + } +} + +``` + +The solver trait hands over self as a read-only reference on purpose. +The solver should be stateless, so it can be better compared. \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a9f9876 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,208 @@ + +mod playground; +mod solver; +mod mysolver; + +use mysolver::MySolver; +use playground::Playground; +use solver::Solver; + +#[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); + let solver = MySolver::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_pg.contains(&pg)); + } + +} +fn main() { + + 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}}"#; + + let pg = Playground::from(field); + + pg.print(); + + let solver = MySolver::new(); + + println!(""); + + if let Some(result) = solver.solve(&pg) { + result.print(); + + 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!") + } + assert!(result.contains(&pg)); + } else { + println!("Solver was not able to resolve the sodoku!") + } + + + + +} diff --git a/src/mysolver.rs b/src/mysolver.rs new file mode 100644 index 0000000..d718dd6 --- /dev/null +++ b/src/mysolver.rs @@ -0,0 +1,18 @@ +use crate::solver::Solver; +use crate::playground::Playground; + +pub struct MySolver { +} + +impl MySolver { + pub fn new() -> MySolver { + return MySolver {}; + } +} + +impl Solver for MySolver { + fn solve(&self, pg: &Playground) -> Option { + // FIXME: Implement! + None + } +} \ No newline at end of file diff --git a/src/playground.rs b/src/playground.rs new file mode 100644 index 0000000..a406638 --- /dev/null +++ b/src/playground.rs @@ -0,0 +1,134 @@ + +use std::collections::HashSet; + +#[derive(Clone)] +pub struct Playground { + arr: [[u8; 9]; 9] +} + +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 { + let mut p = Playground::new(); + p.init(String::from(s)); + p + } + + pub fn print(&self) { + println!("-------------------------------------"); + for y in 0..9 { + print!("|"); + for x in 0..9 { + let value = self.get_value(x, y); + if value.is_some() { + print!(" {} |", value.unwrap()); + } else { + print!(" |"); + } + } + println!(); + + println!("-------------------------------------"); + } + } + + pub fn get_value(&self, x: usize, y: usize) -> Option { + match self.arr[y][x] { + 1..=9 => Some(self.arr[y][x]), + _ => None + } + } + + pub fn set_value(&mut self, x: usize, y: usize, value : u8) { + self.arr[y][x] = value; + } + + pub fn is_solved(&self) -> bool { + return self.arr.iter() + .flat_map(|row| row.iter()) + .all(|value| *value != 0u8); + } + + /** Checks if the playground has failures (not neccesary solved) */ + pub fn is_valid(&self) -> bool { + self.rows_valid() && self.columns_valid() && self.sections_valid() + } + + fn rows_valid(&self) -> bool { + let mut rows_set : HashSet = HashSet::with_capacity(9); + + for x in 0..9 { + rows_set.clear(); + + for y in 0..9 { + if !rows_set.insert(self.arr[y][x]) { + return false + } + } + } + true + } + + fn columns_valid(&self) -> bool { + let mut cols_set : HashSet = HashSet::with_capacity(9); + + for y in 0..9 { + cols_set.clear(); + + for x in 0..9 { + if !cols_set.insert(self.arr[y][x]) { + return false + } + } + } + true + } + + fn sections_valid(&self) -> bool { + let mut sects_set : HashSet = HashSet::with_capacity(9); + + for s in 0..9 { + sects_set.clear(); + let ref_point = ((s * 3) % 9, s / 3 * 3); + + for i in 0..9 { + let (ref_x,ref_y) = ref_point; + if !sects_set.insert(self.arr[ref_y + (i/3)][ref_x + (i%3)]) { + return false + } + } + } + true + } + + pub fn contains(&self, other : &Playground) -> bool { + for x in 0..9 { + for y in 0..9 { + if other.arr[y][x] != 0 && self.arr[y][x] != other.arr[y][x] { + return false; + } + } + } + true + } + +} diff --git a/src/solver.rs b/src/solver.rs new file mode 100644 index 0000000..fd077c8 --- /dev/null +++ b/src/solver.rs @@ -0,0 +1,6 @@ + +use crate::playground::Playground; + +pub trait Solver { + fn solve(&self, _: &Playground) -> Option; +}