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