Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
79
Cargo.lock
generated
Normal file
79
Cargo.lock
generated
Normal file
@@ -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"
|
||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@@ -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"
|
||||
398
src/main.rs
Normal file
398
src/main.rs
Normal file
@@ -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<u8> {
|
||||
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<u8> {
|
||||
let mut v = Vec::<u8>::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<Self::Item> {
|
||||
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<Self::Item> {
|
||||
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<Self::Item> {
|
||||
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<usize> = None;
|
||||
let mut shortest_col : Option<usize> = 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<u8> {
|
||||
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<u8> {
|
||||
match &self.at(row, col) {
|
||||
CellValue::Possible(x) => x.get_values(),
|
||||
CellValue::None => Vec::<u8>::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::<u8>::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<Grid> {
|
||||
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()));
|
||||
}
|
||||
Reference in New Issue
Block a user