sudoku with simple backtracking
This commit is contained in:
commit
9a0a9afe37
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
147
Cargo.lock
generated
Normal file
147
Cargo.lock
generated
Normal file
|
@ -0,0 +1,147 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.166"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sudoku"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"text_io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text_io"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f0c8eb2ad70c12a6a69508f499b3051c924f4b1cfeae85bfad96e6bc5bba46"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "sudoku"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.5"
|
||||
text_io = "0.1.12"
|
18
shell.nix
Normal file
18
shell.nix
Normal file
|
@ -0,0 +1,18 @@
|
|||
{ pkgs ? import <nixpkgs> { overlays = [ (import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz)) ]; },}:
|
||||
with pkgs;
|
||||
|
||||
mkShell {
|
||||
nativeBuildInputs = with xorg; [
|
||||
pkg-config
|
||||
] ++ [
|
||||
cargo
|
||||
rustc
|
||||
];
|
||||
buildInputs = [
|
||||
latest.rustChannels.stable.rust
|
||||
xorg.libX11
|
||||
xorg.libXi
|
||||
xorg.libXtst
|
||||
libevdev
|
||||
];
|
||||
}
|
259
src/main.rs
Normal file
259
src/main.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
use std::fmt;
|
||||
use std::collections::HashSet;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Cell {
|
||||
value: Option<u8>,
|
||||
possibilities: Vec<u8>
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
fn set(&mut self, value: u8) {
|
||||
self.value = if value > 0 && value < 10 {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
fn reset_possibilities(&mut self) {
|
||||
self.possibilities = (1..=9).collect();
|
||||
}
|
||||
|
||||
fn remove_possibilities(&mut self, possibilities: &HashSet<u8>) {
|
||||
self.possibilities.retain(|&x| !possibilities.contains(&x))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
value: None,
|
||||
possibilities: (1..=9).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Cell {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(v) = self.value {
|
||||
write!(f, "{}", v)
|
||||
} else {
|
||||
write!(f, " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Choice {
|
||||
cell_selected: [usize; 2],
|
||||
possibilities: Vec<u8>,
|
||||
selected_value: u8,
|
||||
}
|
||||
|
||||
struct Sudoku {
|
||||
grid: [[Cell; 9]; 9],
|
||||
rng: rand::rngs::ThreadRng,
|
||||
history: Vec<Choice>,
|
||||
}
|
||||
|
||||
impl Sudoku {
|
||||
fn new(sudoku_set: [[u8; 9]; 9]) -> Self {
|
||||
let mut sudoku_grid: [[Cell; 9]; 9] = [[(); 9]; 9].map(|_| [(); 9].map(|_| Cell::default()));
|
||||
|
||||
for (row_index, row) in sudoku_set.iter().enumerate(){
|
||||
for (column_index, value) in row.iter().enumerate() {
|
||||
sudoku_grid[row_index][column_index].set(*value)
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
grid: sudoku_grid,
|
||||
rng: rand::thread_rng(),
|
||||
history: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn display(&self) {
|
||||
println!("# sudoku grid : ");
|
||||
for row in &self.grid {
|
||||
let mut line: String = "[".to_string();
|
||||
for cell in row {
|
||||
line += &format!("{}|",cell);
|
||||
}
|
||||
line.pop();
|
||||
println!("{}]",line);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_possibilities(&mut self) {
|
||||
self.update_rows();
|
||||
self.update_columns();
|
||||
self.update_squares();
|
||||
}
|
||||
|
||||
fn update_rows(&mut self) {
|
||||
for row_index in 0..9 {
|
||||
let mut used_values: HashSet<u8> = HashSet::new();
|
||||
|
||||
for column_index in 0..9 {
|
||||
if let Some(value) = self.grid[row_index][column_index].value {
|
||||
used_values.insert(value);
|
||||
}
|
||||
}
|
||||
|
||||
for column_index in 0..9 {
|
||||
self.grid[row_index][column_index].remove_possibilities(&used_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_columns(&mut self) {
|
||||
for column_index in 0..9 {
|
||||
let mut used_values: HashSet<u8> = HashSet::new();
|
||||
|
||||
for row_index in 0..9 {
|
||||
if let Some(value) = self.grid[row_index][column_index].value {
|
||||
used_values.insert(value);
|
||||
}
|
||||
}
|
||||
|
||||
for row_index in 0..9 {
|
||||
self.grid[row_index][column_index].remove_possibilities(&used_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_squares(&mut self) {
|
||||
for square_row_index in 0..3 {
|
||||
for square_column_index in 0..3 {
|
||||
let mut used_values: HashSet<u8> = HashSet::new();
|
||||
|
||||
for row_offset_index in 0..3 {
|
||||
for column_offset_index in 0..3 {
|
||||
if let Some(value) = self.grid[square_row_index * 3 + row_offset_index][square_column_index * 3 + column_offset_index].value {
|
||||
used_values.insert(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for row_offset_index in 0..3 {
|
||||
for column_offset_index in 0..3 {
|
||||
self.grid[square_row_index * 3 + row_offset_index][square_column_index * 3 + column_offset_index].remove_possibilities(&used_values.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collapse(&mut self) -> bool {
|
||||
let mut min_row_index: usize = 0;
|
||||
let mut min_column_index: usize = 0;
|
||||
let mut min_len: usize = 9;
|
||||
|
||||
let mut grid_has_empty_cell: bool = false;
|
||||
|
||||
for row_index in 0..9 {
|
||||
for column_index in 0..9 {
|
||||
if !self.grid[row_index][column_index].value.is_none() {
|
||||
continue;
|
||||
}
|
||||
grid_has_empty_cell = true;
|
||||
let possibilities_len = self.grid[row_index][column_index].possibilities.len();
|
||||
if possibilities_len < min_len {
|
||||
min_row_index = row_index;
|
||||
min_column_index = column_index;
|
||||
min_len = possibilities_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !grid_has_empty_cell {
|
||||
println!("x no empty cells");
|
||||
return false
|
||||
}
|
||||
|
||||
if self.collapse_cell(min_row_index, min_column_index) {
|
||||
return true
|
||||
}
|
||||
|
||||
let mut fork: Option<Choice> = None;
|
||||
|
||||
while let Some(choice) = self.history.pop() {
|
||||
println!("* backtracking");
|
||||
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].value = None;
|
||||
if choice.possibilities.len() > 1 {
|
||||
fork = Some(choice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(choice) = fork {
|
||||
self.reset_possibilities();
|
||||
let mut choice_value = HashSet::new();
|
||||
choice_value.insert(choice.selected_value);
|
||||
self.grid[choice.cell_selected[0]][choice.cell_selected[1]].remove_possibilities(&choice_value);
|
||||
return self.collapse_cell(choice.cell_selected[0], choice.cell_selected[1])
|
||||
} else {
|
||||
println!("x backtracked to start");
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_possibilities(&mut self) {
|
||||
for row in &mut self.grid {
|
||||
for cell in row {
|
||||
cell.reset_possibilities();
|
||||
}
|
||||
}
|
||||
|
||||
self.update_possibilities();
|
||||
}
|
||||
|
||||
fn collapse_cell(&mut self, row_index: usize, column_index: usize) -> bool {
|
||||
if let Some(&selected_value) = self.grid[row_index][column_index].possibilities.choose(&mut self.rng) {
|
||||
self.history.push(Choice {
|
||||
cell_selected: [row_index, column_index],
|
||||
possibilities: self.grid[row_index][column_index].possibilities.clone(),
|
||||
selected_value,
|
||||
});
|
||||
|
||||
self.grid[row_index][column_index].set(selected_value);
|
||||
println!("# collapsing [{}][{}] ({:?}) to {}", row_index, column_index, self.grid[row_index][column_index].possibilities, selected_value);
|
||||
return true
|
||||
} else {
|
||||
println!("x no possibilities for [{}][{}]", row_index, column_index);
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let sudoku_set: [[u8; 9]; 9] = [
|
||||
[6,0,0,0,0,2,1,0,0],
|
||||
[0,3,2,9,0,0,0,7,0],
|
||||
[0,0,0,0,0,8,0,0,0],
|
||||
[2,0,0,0,0,0,4,0,0],
|
||||
[7,0,3,0,0,0,9,1,0],
|
||||
[0,8,0,0,9,4,0,0,0],
|
||||
[0,0,4,0,0,0,6,0,0],
|
||||
[1,2,0,7,0,0,0,0,5],
|
||||
[0,0,0,0,0,0,0,9,4],
|
||||
];
|
||||
|
||||
let mut sudoku = Sudoku::new(sudoku_set);
|
||||
|
||||
sudoku.display();
|
||||
|
||||
println!("--------");
|
||||
|
||||
sudoku.update_possibilities();
|
||||
|
||||
while sudoku.collapse() {
|
||||
sudoku.update_possibilities();
|
||||
}
|
||||
|
||||
println!("--------");
|
||||
|
||||
sudoku.display();
|
||||
}
|
Loading…
Reference in a new issue