diff --git a/Cargo.lock b/Cargo.lock index a60cade..09bdef5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,9 +8,90 @@ version = "8.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "857057651cdf1fe4bc1e8308493c752db559df0330f23b45f532f6b24c2b443d" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "portable-atomic-util" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdd8420072e66d54a407b3316991fe946ce3ab1083a7f575b2463866624704d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rustdoku" version = "0.1.0" dependencies = [ "assertables", + "ndarray", + "num-traits", ] diff --git a/Cargo.toml b/Cargo.toml index 61ee043..eb9f26a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ edition = "2021" [dependencies] assertables = "8.18.0" +ndarray = "0.16.1" +num-traits = "0.2.19" \ No newline at end of file diff --git a/src/board.rs b/src/board.rs index cef7401..c82d2f0 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,148 +1,86 @@ use crate::cell::{Cell, CellValue}; use std::collections::HashSet; +use ndarray::prelude::*; const REGION_SIZE: usize = 9; const SUB_REGION_SIZE: usize = 3; pub struct Board { - cells: Vec>, - squares: Vec, - rows: Vec, - cols: Vec, + cells: Array2, } impl Board { pub fn new() -> Board { - let mut cells = Vec::with_capacity(REGION_SIZE); - for _ in 0..REGION_SIZE { - let mut col = Vec::with_capacity(REGION_SIZE); - for _ in 0..REGION_SIZE { - col.push(Cell::default()); - } - cells.push(col); - } - let mut squares = Vec::with_capacity(REGION_SIZE); - for x in 0..SUB_REGION_SIZE { - for y in 0..SUB_REGION_SIZE { - squares.push(Region::new_square(&Point(x, y))); - } - } - let mut rows = Vec::with_capacity(REGION_SIZE); - for x in 0..REGION_SIZE { - rows.push(Region::new_row(&Point(x, 0))); - } - let mut cols = Vec::with_capacity(REGION_SIZE); - for y in 0..REGION_SIZE { - cols.push(Region::new_col(&Point(0, y))); - } + let cells = Array2::zeros((REGION_SIZE, REGION_SIZE)); let board = Board { cells, - squares, - rows, - cols }; board } pub fn value(&self, point: &Point) -> Option { - self.cells[point.0][point.1].value() + self.cells[point.coordinates()].value() } pub fn set_value(&mut self, point: &Point, value: CellValue) { - self.cells[point.0][point.1].set_value(value); - self.update_regions(point, value); + self.cells[point.coordinates()].set_value(value); + self.update_related(point, value); } - pub fn update_regions(&mut self, point: &Point, value: CellValue) { - self.update_columns(point, value); - self.update_rows(point, value); - self.update_squares(point, value); + pub fn update_related(&mut self, point: &Point, value: CellValue) { + self.update_column(point, value); + self.update_row(point, value); + self.update_square(point, value); } - pub fn update_rows(&mut self, point: &Point, value: CellValue) { - for row in self.rows.iter_mut() { - if row.includes(point) { - for pt in row.iter_mut() { - self.cells[pt.0][pt.1].remove_candidate(value); - } - } + pub fn update_row(&mut self, point: &Point, value: CellValue) { + for c in self.cells.slice_mut(s!(point.row, ..)) { + c.remove_candidate(value); } } - pub fn update_columns(&mut self, point: &Point, value: CellValue) { - for col in self.cols.iter_mut() { - if col.includes(point) { - for pt in col.iter_mut() { - self.cells[pt.0][pt.1].remove_candidate(value); - } - } + pub fn update_column(&mut self, point: &Point, value: CellValue) { + for c in self.cells.slice_mut(s![.., point.col]) { + c.remove_candidate(value); } } - pub fn update_squares(&mut self, point: &Point, value: CellValue) { - for i in self.squares.iter_mut() { - if i.includes(point) { - for pt in i.iter_mut() { - self.cells[pt.0][pt.1].remove_candidate(value); - } - } + pub fn update_square(&mut self, point: &Point, value: CellValue) { + let row_bounds = point.row_bounds(); + let col_bounds = point.col_bounds(); + for c in self.cells.slice_mut(s![row_bounds.0..row_bounds.1, col_bounds.0..col_bounds.1]) { + c.remove_candidate(value); } } pub fn candidates(&self, point: &Point) -> HashSet { - self.cells[point.0][point.1].candidates() + self.cells[point.coordinates()].candidates() } } #[derive(PartialEq, Debug)] -pub struct Point(usize, usize); - -pub struct Region { - points: Vec, +pub struct Point { + row: usize, + col: usize } -impl Region { - pub fn new_square(start: &Point)-> Region { - let mut points = Vec::with_capacity(REGION_SIZE); - for x in start.0..(start.0 + SUB_REGION_SIZE) { - for y in start.1..(start.1 + SUB_REGION_SIZE) { - points.push(Point(x, y)); - } - } - Region { - points, - } +impl Point { + fn coordinates(&self) -> [usize; 2] { + [self.row, self.col] } - pub fn new_row(start: &Point) -> Region { - let mut points = Vec::with_capacity(REGION_SIZE); - for y in 0..REGION_SIZE { - points.push(Point(start.0, y)); - } + fn _bounds(val: usize) -> (usize, usize) { + let mut start = val / (SUB_REGION_SIZE as usize); + start *= SUB_REGION_SIZE; + (start, start + SUB_REGION_SIZE) + } - Region { points } + fn row_bounds(&self) -> (usize, usize) { + Self::_bounds(self.row) } - pub fn new_col(start: &Point) -> Region { - let mut points = Vec::with_capacity(REGION_SIZE); - for x in 0..REGION_SIZE { - points.push(Point(x, start.1)); - } - Region { points } - } -} - -impl Region { - fn iter(&self) -> std::slice::Iter { - self.points.iter() - } - - fn includes(&self, point: &Point) -> bool { - self.points.contains(point) - } - - fn iter_mut(&mut self) -> std::slice::IterMut { - self.points.iter_mut() + fn col_bounds(&self) -> (usize, usize) { + Self::_bounds(self.col) } } @@ -152,27 +90,40 @@ mod test { use super::*; + #[test] + fn bounds_are_correct() { + let origin = Point{ row: 0, col: 0}; + assert_eq!(origin.row_bounds(), (0, 3)); + assert_eq!(origin.col_bounds(), (0, 3)); + let one = Point{ row: 1, col: 1}; + assert_eq!(one.row_bounds(), (0, 3)); + assert_eq!(one.col_bounds(), (0, 3)); + let five = Point{ row: 5, col: 7}; + assert_eq!(five.row_bounds(), (3, 6)); + assert_eq!(five.col_bounds(), (6, 9)); + } + #[test] fn has_none_values() { let board = Board::new(); - assert!(board.value(&Point(0, 0)).is_none()); + assert!(board.value(&Point{row: 0, col: 0}).is_none()); } #[test] fn set_value_updates_cell() { let mut board = Board::new(); - board.set_value(&Point(0, 0), CellValue::One); - let val = board.value(&Point(0, 0)).unwrap(); + board.set_value(&Point{row: 0, col: 0}, CellValue::One); + let val = board.value(&Point{row: 0, col: 0}).unwrap(); assert_eq!(val, CellValue::One); // Same column - assert_not_contains!(board.candidates(&Point(0, 1)), &CellValue::One); + assert_not_contains!(board.candidates(&Point{row: 1, col: 0}), &CellValue::One); // Same row - assert_not_contains!(board.candidates(&Point(1, 0)), &CellValue::One); + assert_not_contains!(board.candidates(&Point{row: 0, col: 1}), &CellValue::One); // Same sub-structure - assert_not_contains!(board.candidates(&Point(1, 1)), &CellValue::One); + assert_not_contains!(board.candidates(&Point{row: 1, col: 1}), &CellValue::One); // Different row - assert_contains!(board.candidates(&Point(8, 8)), &CellValue::One); + assert_contains!(board.candidates(&Point{row: 8, col: 8}), &CellValue::One); } } diff --git a/src/cell.rs b/src/cell.rs index 884e7dd..33f4439 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -1,4 +1,6 @@ use std::collections::HashSet; +use num_traits::identities::Zero; +use std::ops::Add; #[derive(Clone, Debug, Copy, Hash, Eq, PartialEq)] pub enum CellValue { @@ -13,6 +15,7 @@ pub enum CellValue { Nine, } +#[derive(Clone, Debug)] pub struct Cell { value: Option, candidates: HashSet, @@ -36,8 +39,19 @@ impl Cell { } } -impl Default for Cell { - fn default() -> Cell { +impl Add for Cell { + type Output = Self; + + fn add(self, _other: Self) -> Self { + Self { + value: self.value, + candidates: self.candidates, + } + } +} + +impl Zero for Cell { + fn zero() -> Cell { let mut candidates = HashSet::new(); candidates.insert(CellValue::One); candidates.insert(CellValue::Two); @@ -53,6 +67,10 @@ impl Default for Cell { candidates, } } + + fn is_zero(&self) -> bool { + self.value.is_none() + } } #[cfg(test)] @@ -62,13 +80,13 @@ mod test { #[test] fn inits_empty() { - let cell = Cell::default(); + let cell = Cell::zero(); assert!(cell.value().is_none()); } #[test] fn has_all_candidates() { - let cell = Cell::default(); + let cell = Cell::zero(); assert_contains!(cell.candidates(), &CellValue::One); assert_contains!(cell.candidates(), &CellValue::Two); assert_contains!(cell.candidates(), &CellValue::Three); @@ -82,7 +100,7 @@ mod test { #[test] fn removed_candidate_is_gone() { - let mut cell = Cell::default(); + let mut cell = Cell::zero(); cell.remove_candidate(CellValue::One); assert_not_contains!(cell.candidates(), &CellValue::One); assert_contains!(cell.candidates(), &CellValue::Two);