Convert to using ndarray

This commit is contained in:
Greg Hellings
2024-10-16 08:45:37 -05:00
parent 9af499c585
commit d431a9378a
4 changed files with 163 additions and 111 deletions
Generated
+81
View File
@@ -8,9 +8,90 @@ version = "8.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "857057651cdf1fe4bc1e8308493c752db559df0330f23b45f532f6b24c2b443d" 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]] [[package]]
name = "rustdoku" name = "rustdoku"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"assertables", "assertables",
"ndarray",
"num-traits",
] ]
+2
View File
@@ -5,3 +5,5 @@ edition = "2021"
[dependencies] [dependencies]
assertables = "8.18.0" assertables = "8.18.0"
ndarray = "0.16.1"
num-traits = "0.2.19"
+56 -105
View File
@@ -1,148 +1,86 @@
use crate::cell::{Cell, CellValue}; use crate::cell::{Cell, CellValue};
use std::collections::HashSet; use std::collections::HashSet;
use ndarray::prelude::*;
const REGION_SIZE: usize = 9; const REGION_SIZE: usize = 9;
const SUB_REGION_SIZE: usize = 3; const SUB_REGION_SIZE: usize = 3;
pub struct Board { pub struct Board {
cells: Vec<Vec<Cell>>, cells: Array2<Cell>,
squares: Vec<Region>,
rows: Vec<Region>,
cols: Vec<Region>,
} }
impl Board { impl Board {
pub fn new() -> Board { pub fn new() -> Board {
let mut cells = Vec::with_capacity(REGION_SIZE); let cells = Array2::zeros((REGION_SIZE, 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 board = Board { let board = Board {
cells, cells,
squares,
rows,
cols
}; };
board board
} }
pub fn value(&self, point: &Point) -> Option<CellValue> { pub fn value(&self, point: &Point) -> Option<CellValue> {
self.cells[point.0][point.1].value() self.cells[point.coordinates()].value()
} }
pub fn set_value(&mut self, point: &Point, value: CellValue) { pub fn set_value(&mut self, point: &Point, value: CellValue) {
self.cells[point.0][point.1].set_value(value); self.cells[point.coordinates()].set_value(value);
self.update_regions(point, value); self.update_related(point, value);
} }
pub fn update_regions(&mut self, point: &Point, value: CellValue) { pub fn update_related(&mut self, point: &Point, value: CellValue) {
self.update_columns(point, value); self.update_column(point, value);
self.update_rows(point, value); self.update_row(point, value);
self.update_squares(point, value); self.update_square(point, value);
} }
pub fn update_rows(&mut self, point: &Point, value: CellValue) { pub fn update_row(&mut self, point: &Point, value: CellValue) {
for row in self.rows.iter_mut() { for c in self.cells.slice_mut(s!(point.row, ..)) {
if row.includes(point) { c.remove_candidate(value);
for pt in row.iter_mut() {
self.cells[pt.0][pt.1].remove_candidate(value);
}
}
} }
} }
pub fn update_columns(&mut self, point: &Point, value: CellValue) { pub fn update_column(&mut self, point: &Point, value: CellValue) {
for col in self.cols.iter_mut() { for c in self.cells.slice_mut(s![.., point.col]) {
if col.includes(point) { c.remove_candidate(value);
for pt in col.iter_mut() {
self.cells[pt.0][pt.1].remove_candidate(value);
}
}
} }
} }
pub fn update_squares(&mut self, point: &Point, value: CellValue) { pub fn update_square(&mut self, point: &Point, value: CellValue) {
for i in self.squares.iter_mut() { let row_bounds = point.row_bounds();
if i.includes(point) { let col_bounds = point.col_bounds();
for pt in i.iter_mut() { for c in self.cells.slice_mut(s![row_bounds.0..row_bounds.1, col_bounds.0..col_bounds.1]) {
self.cells[pt.0][pt.1].remove_candidate(value); c.remove_candidate(value);
}
}
} }
} }
pub fn candidates(&self, point: &Point) -> HashSet<CellValue> { pub fn candidates(&self, point: &Point) -> HashSet<CellValue> {
self.cells[point.0][point.1].candidates() self.cells[point.coordinates()].candidates()
} }
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct Point(usize, usize); pub struct Point {
row: usize,
pub struct Region { col: usize
points: Vec<Point>,
} }
impl Region { impl Point {
pub fn new_square(start: &Point)-> Region { fn coordinates(&self) -> [usize; 2] {
let mut points = Vec::with_capacity(REGION_SIZE); [self.row, self.col]
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,
}
} }
pub fn new_row(start: &Point) -> Region { fn _bounds(val: usize) -> (usize, usize) {
let mut points = Vec::with_capacity(REGION_SIZE); let mut start = val / (SUB_REGION_SIZE as usize);
for y in 0..REGION_SIZE { start *= SUB_REGION_SIZE;
points.push(Point(start.0, y)); (start, start + SUB_REGION_SIZE)
}
Region { points }
} }
pub fn new_col(start: &Point) -> Region { fn row_bounds(&self) -> (usize, usize) {
let mut points = Vec::with_capacity(REGION_SIZE); Self::_bounds(self.row)
for x in 0..REGION_SIZE {
points.push(Point(x, start.1));
}
Region { points }
}
}
impl Region {
fn iter(&self) -> std::slice::Iter<Point> {
self.points.iter()
} }
fn includes(&self, point: &Point) -> bool { fn col_bounds(&self) -> (usize, usize) {
self.points.contains(point) Self::_bounds(self.col)
}
fn iter_mut(&mut self) -> std::slice::IterMut<Point> {
self.points.iter_mut()
} }
} }
@@ -152,27 +90,40 @@ mod test {
use super::*; 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] #[test]
fn has_none_values() { fn has_none_values() {
let board = Board::new(); let board = Board::new();
assert!(board.value(&Point(0, 0)).is_none()); assert!(board.value(&Point{row: 0, col: 0}).is_none());
} }
#[test] #[test]
fn set_value_updates_cell() { fn set_value_updates_cell() {
let mut board = Board::new(); let mut board = Board::new();
board.set_value(&Point(0, 0), CellValue::One); board.set_value(&Point{row: 0, col: 0}, CellValue::One);
let val = board.value(&Point(0, 0)).unwrap(); let val = board.value(&Point{row: 0, col: 0}).unwrap();
assert_eq!(val, CellValue::One); assert_eq!(val, CellValue::One);
// Same column // 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 // 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 // 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 // Different row
assert_contains!(board.candidates(&Point(8, 8)), &CellValue::One); assert_contains!(board.candidates(&Point{row: 8, col: 8}), &CellValue::One);
} }
} }
+23 -5
View File
@@ -1,4 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use num_traits::identities::Zero;
use std::ops::Add;
#[derive(Clone, Debug, Copy, Hash, Eq, PartialEq)] #[derive(Clone, Debug, Copy, Hash, Eq, PartialEq)]
pub enum CellValue { pub enum CellValue {
@@ -13,6 +15,7 @@ pub enum CellValue {
Nine, Nine,
} }
#[derive(Clone, Debug)]
pub struct Cell { pub struct Cell {
value: Option<CellValue>, value: Option<CellValue>,
candidates: HashSet<CellValue>, candidates: HashSet<CellValue>,
@@ -36,8 +39,19 @@ impl Cell {
} }
} }
impl Default for Cell { impl Add for Cell {
fn default() -> 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(); let mut candidates = HashSet::new();
candidates.insert(CellValue::One); candidates.insert(CellValue::One);
candidates.insert(CellValue::Two); candidates.insert(CellValue::Two);
@@ -53,6 +67,10 @@ impl Default for Cell {
candidates, candidates,
} }
} }
fn is_zero(&self) -> bool {
self.value.is_none()
}
} }
#[cfg(test)] #[cfg(test)]
@@ -62,13 +80,13 @@ mod test {
#[test] #[test]
fn inits_empty() { fn inits_empty() {
let cell = Cell::default(); let cell = Cell::zero();
assert!(cell.value().is_none()); assert!(cell.value().is_none());
} }
#[test] #[test]
fn has_all_candidates() { fn has_all_candidates() {
let cell = Cell::default(); let cell = Cell::zero();
assert_contains!(cell.candidates(), &CellValue::One); assert_contains!(cell.candidates(), &CellValue::One);
assert_contains!(cell.candidates(), &CellValue::Two); assert_contains!(cell.candidates(), &CellValue::Two);
assert_contains!(cell.candidates(), &CellValue::Three); assert_contains!(cell.candidates(), &CellValue::Three);
@@ -82,7 +100,7 @@ mod test {
#[test] #[test]
fn removed_candidate_is_gone() { fn removed_candidate_is_gone() {
let mut cell = Cell::default(); let mut cell = Cell::zero();
cell.remove_candidate(CellValue::One); cell.remove_candidate(CellValue::One);
assert_not_contains!(cell.candidates(), &CellValue::One); assert_not_contains!(cell.candidates(), &CellValue::One);
assert_contains!(cell.candidates(), &CellValue::Two); assert_contains!(cell.candidates(), &CellValue::Two);