From 3b523257f08f43b2b0137b5a73a77c81aa4c3751 Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Mon, 29 Jan 2024 15:53:06 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 79 +++++++++++ Cargo.toml | 9 ++ src/main.rs | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 487 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fa8ce28 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,79 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arr_macro" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c49336e062fa2ae8aca17a2f99c34d9c1a5d30827e8aff1cb4c294f253afe992" +dependencies = [ + "arr_macro_impl", + "proc-macro-hack", + "proc-macro-nested", +] + +[[package]] +name = "arr_macro_impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6368f9ae5c6ec403ca910327ae0c9437b0a85255b6950c90d497e6177f6e5e" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rudoku" +version = "0.1.0" +dependencies = [ + "arr_macro", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..13768e0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rudoku" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +arr_macro = "0.2.1" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..242faaf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,398 @@ +use std::fmt; +use arr_macro::arr; + +const GRID_SIZE: usize = 9; + +#[derive(Debug, PartialEq)] +#[derive(Clone, Copy)] +pub struct PossibleValues([bool; GRID_SIZE]); + +impl PossibleValues { + fn get_value(&self) -> Option { + let mut val = 0u8; + for x in 0..GRID_SIZE { + if self.0[x] { + if val != 0 { panic!("get_value called on PossibleValue with more than one possiblitiy {}", self) } + val = (x+1) as u8; + } + } + if val == 0 { None } else { Some(val) } + } + + fn get_values(&self) -> Vec { + let mut v = Vec::::new(); + for x in 0..GRID_SIZE { + if self.0[x] { + v.push((x+1) as u8) + } + } + v + } + + fn get_count(&self) -> u8 { + let mut count = 0u8; + for x in 0..GRID_SIZE { + if self.0[x] { + count += 1; + } + } + count + } +} + +impl fmt::Display for PossibleValues { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad(format!("{:?}", self.get_values()).as_str()) + } +} + +#[derive(Debug, PartialEq)] +#[derive(Clone, Copy)] +pub enum CellValue { + None, + Fixed(u8), + Possible(PossibleValues), +} + +impl CellValue { + fn from_possible(possible: PossibleValues) -> CellValue { + let cnt = possible.get_count(); + match cnt { + 0 => CellValue::None, + 1 => CellValue::Fixed(possible.get_value().unwrap()), + _ => CellValue::Possible(possible), + } + } +} + +impl fmt::Display for CellValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + CellValue::Possible(x) => write!(f, "{:27}", x), + CellValue::None => write!(f, "{:27}", "X"), + CellValue::Fixed(x) => write!(f, "{:<27}", x), + } + } +} + +#[derive(Debug, PartialEq)] +#[derive(Clone, Copy)] +pub struct Grid { + data: [CellValue; GRID_SIZE * GRID_SIZE], +} + +pub struct GridRowIterator<'a> { + parent: &'a Grid, + pos: usize, + row: usize, +} + +impl<'a> Iterator for GridRowIterator<'a> { + type Item = CellValue; + + fn next(&mut self) -> Option { + if self.pos >= GRID_SIZE { return None; } + let curr = self.parent.at(self.row, self.pos); + self.pos += 1; + Some(*curr) + } +} + +pub struct GridColIterator<'a> { + parent: &'a Grid, + pos: usize, + col: usize, +} + +impl<'a> Iterator for GridColIterator<'a> { + type Item = CellValue; + + fn next(&mut self) -> Option { + if self.pos >= GRID_SIZE { return None; } + let curr = self.parent.at(self.pos, self.col); + self.pos += 1; + Some(*curr) + } +} + +pub struct GridBlockIterator<'a> { + parent: &'a Grid, + pos: usize, + row: usize, + col: usize +} + +impl<'a> Iterator for GridBlockIterator<'a> { + type Item = CellValue; + + fn next(&mut self) -> Option { + if self.pos >= GRID_SIZE { return None; } + let curr = self.parent.at(self.row + self.pos % 3, self.col + self.pos / 3); + self.pos += 1; + Some(*curr) + } +} + +impl Grid { + pub fn new() -> Grid { + Grid{data: arr![CellValue::None; 81]} + } + + pub fn from_array(input: [u8; 81]) -> Grid { + let mut g = Grid::new(); + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + match input[row * GRID_SIZE + col] { + 0 => *g.at_mut(row, col) = CellValue::None, + other => *g.at_mut(row, col) = CellValue::Fixed(other) + } + } + } + g.update(); + g + } + + pub fn row_iter(&self, row: usize) -> GridRowIterator { + GridRowIterator{parent: self, pos: 0, row} + } + + pub fn col_iter(&self, col: usize) -> GridColIterator { + GridColIterator{parent: self, pos: 0, col} + } + + pub fn block_iter(&self, row: usize, col: usize) -> GridBlockIterator { + let row = (row / 3) * 3; + let col = (col / 3) * 3; + GridBlockIterator{parent: self, pos: 0, row, col} + } + + // For all cells that are not fixed, compute possible values + pub fn update(&mut self) { + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + if let CellValue::Fixed(_) = &self.at(row, col) { continue; } + *self.at_mut(row, col) = self.compute_possible(row, col); + } + } + } + + // Find the shortest list of possible values + pub fn find_shortest_possibles(&self) -> Option<(usize, usize, PossibleValues)> { + let mut len = 9; + let mut shortest_row : Option = None; + let mut shortest_col : Option = None; + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + if let CellValue::Possible(x) = &self.at(row, col) { + if x.get_count() <= len { + len = x.get_count(); + shortest_row = Some(row); + shortest_col = Some(col); + } + } + } + } + if let Some(row) = shortest_row { + let col = shortest_col.unwrap(); + let CellValue::Possible(val) = &self.at(row, col) else { panic!(); }; + Some((row, col, *val)) + } else { + None + } + } + + pub fn done(&self) -> bool { + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + match &self.at(row, col) { + CellValue::Possible(_) => return false, + CellValue::None => return false, + CellValue::Fixed(_) => continue, + } + } + } + return true; + } + + pub fn possible(&self) -> bool { + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + match &self.at(row, col) { + CellValue::None => return false, + _ => continue, + } + } + } + return true; + } + + pub fn at(&self, row: usize, col: usize) -> &CellValue { + if row >= GRID_SIZE || col >= GRID_SIZE { panic!("out of bounds access"); } + &self.data[row*GRID_SIZE + col] + } + + pub fn at_mut(&mut self, row: usize, col: usize) -> &mut CellValue { + if row >= GRID_SIZE || col >= GRID_SIZE { panic!("out of bounds access"); } + &mut self.data[row*GRID_SIZE + col] + } + + fn get_value(&self, row: usize, col: usize) -> Option { + match &self.at(row, col) { + CellValue::Possible(x) => x.get_value(), + CellValue::None => None, + CellValue::Fixed(x) => Some(*x) + } + } + + fn get_values(&self, row: usize, col: usize) -> Vec { + match &self.at(row, col) { + CellValue::Possible(x) => x.get_values(), + CellValue::None => Vec::::new(), + CellValue::Fixed(x) => vec![*x], + } + } + + fn compute_possible(&self, row: usize, col: usize) -> CellValue { + if let CellValue::Fixed(x) = &self.at(row, col) { return CellValue::Fixed(*x); } + let mut result = PossibleValues([true; 9]); + for other in self.row_iter(row) { + if let CellValue::Fixed(x) = other { result.0[(x-1) as usize] = false } + } + for other in self.col_iter(col) { + if let CellValue::Fixed(x) = other { result.0[(x-1) as usize] = false } + } + for other in self.block_iter(row, col) { + if let CellValue::Fixed(x) = other { result.0[(x-1) as usize] = false } + } + CellValue::from_possible(result) + } +} + +impl fmt::Display for Grid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for row in 0..GRID_SIZE { + for col in 0..GRID_SIZE { + write!(f, "{}", &self.at(row, col))?; + if col < GRID_SIZE-1 { + if (col + 1) % 3 == 0 { + write!(f, " || ")?; + } else { + write!(f, " | ")?; + } + } + } + if row < GRID_SIZE-1 { + if (row + 1) % 3 == 0 { + write!(f, "\n{}", "-".repeat(29*9+2))?; + } + writeln!(f)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn possible_values() { + let none = PossibleValues([false; 9]); + let three = PossibleValues([false, false, true, false, false, false, false, false, false]); + let some = PossibleValues([false, true, false, true, false, false, false, false, false]); + let all = PossibleValues([true; 9]); + + assert_eq!(None, none.get_value()); + assert_eq!(Some(3), three.get_value()); + + assert_eq!(0, none.get_count()); + assert_eq!(1, three.get_count()); + assert_eq!(2, some.get_count()); + assert_eq!(9, all.get_count()); + + assert_eq!(Vec::::new(), none.get_values()); + assert_eq!(vec![3u8], three.get_values()); + assert_eq!(vec![2u8, 4u8], some.get_values()); + assert_eq!(vec![1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8, 9u8], all.get_values()); + } + + #[test] + #[should_panic] + fn possible_values_get_value() { + let some = PossibleValues([false, true, false, true, false, false, false, false, false]); + some.get_value(); + } + + #[test] + fn cellvalue_from_possible() { + let none = PossibleValues([false; 9]); + let three = PossibleValues([false, false, true, false, false, false, false, false, false]); + let some = PossibleValues([false, true, false, true, false, false, false, false, false]); + let all = PossibleValues([true; 9]); + + assert_eq!(CellValue::None, CellValue::from_possible(none)); + assert_eq!(CellValue::Fixed(3), CellValue::from_possible(three)); + assert_eq!(CellValue::Possible(some), CellValue::from_possible(some)); + assert_eq!(CellValue::Possible(all), CellValue::from_possible(all)); + } + + #[test] + fn new_grid_is_empty() { + let g = Grid::new(); + for row in 0..9 { + for col in 0..9 { + assert_eq!(None, g.get_value(row, col)); + } + } + } +} + +fn solve(g: Grid) -> Option { + let mut queue = vec![g]; + while let Some(mut work) = queue.pop() { + if !work.possible() { continue; } + let prev = work; + println!("{work}"); + println!(); + work.update(); + if work.done() { + return Some(work); + } else if prev == work { + let Some((row, col, possibles)) = work.find_shortest_possibles() else { continue; }; + for possible in possibles.get_values() { + let mut copy = work; + *copy.at_mut(row, col) = CellValue::Fixed(possible); + copy.update(); + queue.push(copy); + } + } else { + queue.push(work); + } + } + None +} + +fn main() { + let g = Grid::from_array([5, 3, 0, 0, 7, 0, 0, 0, 0, + 6, 0, 0, 1, 9, 5, 0, 0, 0, + 0, 9, 8, 0, 0, 0, 0, 6, 0, + 8, 0, 0, 0, 6, 0, 0, 0, 3, + 4, 0, 0, 8, 0, 3, 0, 0, 1, + 7, 0, 0, 0, 2, 0, 0, 0, 6, + 0, 6, 0, 0, 0, 0, 2, 8, 0, + 0, 0, 0, 4, 1, 9, 0, 0, 5, + 0, 0, 0, 0, 8, 0, 0, 7, 9]); + println!("{}\n", solve(g).map(|g| format!("{}", g)).unwrap_or("No solution".to_string())); + + let evil = Grid::from_array([0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3, 0, 8, 5, + 0, 0, 1, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 5, 0, 7, 0, 0, 0, + 0, 0, 4, 0, 0, 0, 1, 0, 0, + 0, 9, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 7, 3, + 0, 0, 2, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 0, 0, 0, 9]); + println!("{}\n", solve(evil).map(|g| format!("{}", g)).unwrap_or("No solution".to_string())); +}