Skip to content

Commit

Permalink
StochasticSolver
Browse files Browse the repository at this point in the history
  • Loading branch information
Nervonment committed Jun 1, 2024
1 parent f5d9e3b commit cdde051
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 28 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ rand = "0.8.5"
name = "benchmark"
harness = false

[[bench]]
name = "neo_solvers"
harness = false

[[bin]]
name = "example"
test = false
Expand Down
21 changes: 21 additions & 0 deletions benches/neo_solvers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use criterion::{criterion_group, criterion_main, Criterion};
use sudoku::{
game::generator::random_sudoku_puzzle_normal,
neo::{
puzzle::SudokuPuzzleSimple,
solver::{Solver, StochasticSolver},
},
};

fn benchmarks(c: &mut Criterion) {
let puzzle = random_sudoku_puzzle_normal();
let mut solver = StochasticSolver::<SudokuPuzzleSimple>::new(puzzle);
c.bench_function("StochasticSolver", |b| {
b.iter(|| {
solver.any_solution();
})
});
}

criterion_group!(benches, benchmarks);
criterion_main!(benches);
4 changes: 2 additions & 2 deletions src/bin/example/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crossterm::{terminal::EnterAlternateScreen, ExecutableCommand};
use sudoku::{
game::generator::random_sudoku_puzzle_normal,
neo::{
puzzle::SudokuPuzzle,
puzzle::{Grid, SudokuPuzzleFull},
techniques::{
hidden_pair_blk, hidden_pair_col, hidden_pair_row, hidden_single_blk,
hidden_single_col, hidden_single_row, naked_pair_blk, naked_pair_col, naked_pair_row,
Expand All @@ -27,7 +27,7 @@ fn main() -> io::Result<()> {
// [0, 5, 0, 6, 2, 0, 0, 0, 0],
// [7, 0, 2, 0, 5, 0, 0, 0, 0],
// ];
let puzzle = SudokuPuzzle::new(board);
let puzzle = SudokuPuzzleFull::new(board);
stdout().execute(EnterAlternateScreen)?;
draw_grid()?;
draw_numbers(&board, &board, &[[true; 9]; 9])?;
Expand Down
4 changes: 3 additions & 1 deletion src/neo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ pub mod puzzle;
#[cfg(test)]
pub mod test;
pub mod utils;
pub mod techniques;
pub mod techniques;
pub mod solver;
pub mod judge;
46 changes: 46 additions & 0 deletions src/neo/judge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use super::utils::coord_2_block;

// 返回 (
// board是否为有效的部分解
// board是否为有效的完全解
// board中违反约束的格子
// )
pub fn judge_sudoku(board: &[[i8; 9]; 9]) -> (bool, bool, [[bool; 9]; 9]) {
let mut row: [[(i8, i8); 10]; 9] = [[(-1, -1); 10]; 9];
let mut col: [[(i8, i8); 10]; 9] = [[(-1, -1); 10]; 9];
let mut block: [[(i8, i8); 10]; 9] = [[(-1, -1); 10]; 9];
let mut valid = true;
let mut full = true;
let mut valid_cond = [[true; 9]; 9];
for r in 0..9 {
for c in 0..9 {
if board[r][c] > 0 {
let b = coord_2_block(r, c);
if row[r][board[r][c] as usize] != (-1, -1) {
valid = false;
valid_cond[r][c] = false;
let (r1, c1) = row[r][board[r][c] as usize];
valid_cond[r1 as usize][c1 as usize] = false;
}
if col[c][board[r][c] as usize] != (-1, -1) {
valid = false;
valid_cond[r][c] = false;
let (r1, c1) = col[c][board[r][c] as usize];
valid_cond[r1 as usize][c1 as usize] = false;
}
if block[b][board[r][c] as usize] != (-1, -1) {
valid = false;
valid_cond[r][c] = false;
let (r1, c1) = block[b][board[r][c] as usize];
valid_cond[r1 as usize][c1 as usize] = false;
}
row[r][board[r][c] as usize] = (r as i8, c as i8);
col[c][board[r][c] as usize] = (r as i8, c as i8);
block[b][board[r][c] as usize] = (r as i8, c as i8);
} else {
full = false;
}
}
}
(valid, valid && full, valid_cond)
}
91 changes: 84 additions & 7 deletions src/neo/puzzle.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
use super::utils::{block_idx_2_coord, coord_2_block};

pub trait Grid {
fn new(puzzle: [[i8; 9]; 9]) -> Self;
fn grid_val(&self, r: usize, c: usize) -> i8;
fn is_grid_empty(&self, r: usize, c: usize) -> bool;
fn board(&self) -> [[i8; 9]; 9];
}

pub trait TrackingCandidates {
fn is_candidate_of(&self, r: usize, c: usize, num: i8) -> bool;
}

pub trait TrackingCandidateCountForGrid {
fn candidate_cnt_for_grid(&self, r: usize, c: usize) -> i8;
fn candidate_cnt_for_grid_in_row(&self, r: usize, c: usize) -> i8;
fn candidate_cnt_for_grid_in_col(&self, c: usize, r: usize) -> i8;
fn candidate_cnt_for_grid_in_blk(&self, b: usize, bidx: usize) -> i8;
}

pub trait TrackingGridCountForCandidate {
fn grid_cnt_for_candidate_in_row(&self, r: usize, num: i8) -> i8;
fn grid_cnt_for_candidate_in_col(&self, c: usize, num: i8) -> i8;
fn grid_cnt_for_candidate_in_blk(&self, b: usize, num: i8) -> i8;
Expand All @@ -23,7 +29,71 @@ pub trait Fillable {
fn unfill_grid(&mut self, r: usize, c: usize);
}

pub struct SudokuPuzzle {
pub struct SudokuPuzzleSimple {
board: [[i8; 9]; 9], // 棋盘
row: [[bool; 10]; 9], // row[r][num] = 第r行是否存在数num
col: [[bool; 10]; 9], // 同理
block: [[bool; 10]; 9], // 同理
}

impl Grid for SudokuPuzzleSimple {
fn new(puzzle: [[i8; 9]; 9]) -> Self {
let mut res = Self {
board: puzzle,
row: [[false; 10]; 9],
col: [[false; 10]; 9],
block: [[false; 10]; 9],
};
for r in 0..9 {
for c in 0..9 {
res.row[r][res.board[r][c] as usize] = true;
res.col[c][res.board[r][c] as usize] = true;
res.block[coord_2_block(r, c)][res.board[r][c] as usize] = true;
}
}
res
}

fn grid_val(&self, r: usize, c: usize) -> i8 {
self.board[r][c]
}

fn is_grid_empty(&self, r: usize, c: usize) -> bool {
self.board[r][c] == 0
}

fn board(&self) -> [[i8; 9]; 9] {
self.board
}
}

impl TrackingCandidates for SudokuPuzzleSimple {
fn is_candidate_of(&self, r: usize, c: usize, num: i8) -> bool {
let b = coord_2_block(r, c);
!self.row[r][num as usize] && !self.col[c][num as usize] && !self.block[b][num as usize]
}
}

impl Fillable for SudokuPuzzleSimple {
fn fill_grid(&mut self, r: usize, c: usize, num: i8) {
let b = coord_2_block(r, c);
self.board[r][c] = num;
self.row[r][num as usize] = true;
self.col[c][num as usize] = true;
self.block[b][num as usize] = true;
}

fn unfill_grid(&mut self, r: usize, c: usize) {
let b = coord_2_block(r, c);
let num = self.board[r][c];
self.board[r][c] = 0;
self.row[r][num as usize] = false;
self.col[c][num as usize] = false;
self.block[b][num as usize] = false;
}
}

pub struct SudokuPuzzleFull {
board: [[i8; 9]; 9],
candidates: [[[bool; 10]; 9]; 9],
candidate_cnt: [[i8; 9]; 9],
Expand All @@ -39,11 +109,13 @@ pub struct SudokuPuzzle {
)>,
}

impl TrackingCandidates for SudokuPuzzle {
impl TrackingCandidates for SudokuPuzzleFull {
fn is_candidate_of(&self, r: usize, c: usize, num: i8) -> bool {
self.candidates[r][c][num as usize]
}
}

impl TrackingCandidateCountForGrid for SudokuPuzzleFull {
fn candidate_cnt_for_grid(&self, r: usize, c: usize) -> i8 {
self.candidate_cnt[r][c]
}
Expand All @@ -57,7 +129,9 @@ impl TrackingCandidates for SudokuPuzzle {
let (r, c) = block_idx_2_coord(b, bidx);
self.candidate_cnt[r][c]
}
}

impl TrackingGridCountForCandidate for SudokuPuzzleFull {
fn grid_cnt_for_candidate_in_row(&self, r: usize, num: i8) -> i8 {
self.grid_cnt_for_candidate_in_row[r][num as usize]
}
Expand All @@ -69,7 +143,7 @@ impl TrackingCandidates for SudokuPuzzle {
}
}

impl Fillable for SudokuPuzzle {
impl Fillable for SudokuPuzzleFull {
// 在格 (r, c) 处填上 num
fn fill_grid(&mut self, r: usize, c: usize, num: i8) {
// 记录历史状态
Expand Down Expand Up @@ -151,8 +225,8 @@ impl Fillable for SudokuPuzzle {
}
}

impl SudokuPuzzle {
pub fn new(puzzle: [[i8; 9]; 9]) -> Self {
impl Grid for SudokuPuzzleFull {
fn new(puzzle: [[i8; 9]; 9]) -> Self {
let mut res = Self {
board: [[0; 9]; 9],
candidates: [[[true; 10]; 9]; 9],
Expand All @@ -171,13 +245,16 @@ impl SudokuPuzzle {
}
res
}
}

impl Grid for SudokuPuzzle {
fn grid_val(&self, r: usize, c: usize) -> i8 {
self.board[r][c]
}

fn is_grid_empty(&self, r: usize, c: usize) -> bool {
self.board[r][c] == 0
}

fn board(&self) -> [[i8; 9]; 9] {
self.board
}
}
103 changes: 103 additions & 0 deletions src/neo/solver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use super::puzzle::{Fillable, Grid, TrackingCandidates};
use rand::prelude::*;

pub trait Solver {
fn new(puzzle: [[i8; 9]; 9]) -> Self;
fn any_solution(&mut self) -> Option<[[i8; 9]; 9]>;
fn solution_cnt(&mut self) -> u32;
fn have_unique_solution(&mut self) -> bool;
}

pub struct StochasticSolver<T>
where
T: Grid + Fillable + TrackingCandidates,
{
puzzle_arr: [[i8; 9]; 9],
puzzle: T,
solution_cnt: u32,
}

impl<T> StochasticSolver<T>
where
T: Grid + Fillable + TrackingCandidates,
{
fn init_search(&mut self) {
self.solution_cnt = 0;
self.puzzle = T::new(self.puzzle_arr);
}

fn next_blank(&self, mut row: usize, mut col: usize) -> Option<(usize, usize)> {
while row < 9 && !self.puzzle.is_grid_empty(row, col) {
if col == 8 {
col = 0;
row += 1;
} else {
col += 1
}
}
if row == 9 {
return None;
}
Some((row, col))
}

fn search(&mut self, r: usize, c: usize, solution_cnt_needed: u32) -> bool {
let coord = self.next_blank(r, c);
if coord.is_none() {
self.solution_cnt += 1;
return true;
}
let (r, c) = coord.unwrap();

let mut nums: Vec<i8> = (1..=9).collect();
nums.shuffle(&mut rand::thread_rng());
for num in nums {
if self.puzzle.is_candidate_of(r, c, num) {
self.puzzle.fill_grid(r, c, num);

if self.search(r, c, solution_cnt_needed)
&& solution_cnt_needed <= self.solution_cnt
{
return true;
}

self.puzzle.unfill_grid(r, c);
}
}

false
}
}

impl<T> Solver for StochasticSolver<T>
where
T: Grid + Fillable + TrackingCandidates,
{
fn new(puzzle: [[i8; 9]; 9]) -> Self {
Self {
puzzle_arr: puzzle,
puzzle: T::new(puzzle),
solution_cnt: 0,
}
}

fn any_solution(&mut self) -> Option<[[i8; 9]; 9]> {
self.init_search();
if self.search(0, 0, 1) {
return Some(self.puzzle.board());
}
None
}

fn solution_cnt(&mut self) -> u32 {
self.init_search();
self.search(0, 0, u32::MAX);
self.solution_cnt
}

fn have_unique_solution(&mut self) -> bool {
self.init_search();
self.search(0, 0, 2);
self.solution_cnt == 1
}
}
Loading

0 comments on commit cdde051

Please sign in to comment.