Skip to content

Commit

Permalink
TechniquesSolver
Browse files Browse the repository at this point in the history
  • Loading branch information
Nervonment committed Jun 1, 2024
1 parent cdde051 commit b1f139a
Show file tree
Hide file tree
Showing 6 changed files with 380 additions and 90 deletions.
17 changes: 9 additions & 8 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use sudoku::game::{
generator::{
random_sudoku_puzzle_easy, random_sudoku_puzzle_extraeasy, random_sudoku_puzzle_extrahard,
random_sudoku_puzzle_hard, random_sudoku_puzzle_normal,
},
}, solver::{SudokuSolver, SudokuSolverH},
// solver::{SudokuSolver, SudokuSolverH, SudokuSolverHP},
};

Expand All @@ -19,13 +19,14 @@ pub fn benchmarks(c: &mut Criterion) {
// [0, 6, 0, 0, 0, 4, 0, 2, 0],
// [0, 0, 9, 0, 0, 0, 0, 0, 5],
// ];
// // let puzzle = random_sudoku_puzzle(80);
// let mut solver = SudokuSolverH::new(puzzle);
// c.bench_function("SudokuSolverH", |b| {
// b.iter(|| {
// solver.get_solution();
// })
// });
// let puzzle = random_sudoku_puzzle(80);
let puzzle = random_sudoku_puzzle_normal();
let mut solver = SudokuSolverH::new(puzzle);
c.bench_function("SudokuSolverH", |b| {
b.iter(|| {
solver.get_solution();
})
});
// let mut solver_hp = SudokuSolverHP::new(puzzle);
// c.bench_function("SudoluSolverHP", |b| {
// b.iter(|| {
Expand Down
10 changes: 8 additions & 2 deletions benches/neo_solvers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use criterion::{criterion_group, criterion_main, Criterion};
use sudoku::{
game::generator::random_sudoku_puzzle_normal,
neo::{
puzzle::SudokuPuzzleSimple,
solver::{Solver, StochasticSolver},
puzzle::{SudokuPuzzleFull, SudokuPuzzleSimple},
solver::{Solver, StochasticSolver, TechniquesSolver},
},
};

Expand All @@ -15,6 +15,12 @@ fn benchmarks(c: &mut Criterion) {
solver.any_solution();
})
});
let mut solver = TechniquesSolver::<SudokuPuzzleFull>::new(puzzle);
c.bench_function("TechniquesSolver", |b| {
b.iter(|| {
solver.any_solution();
})
});
}

criterion_group!(benches, benchmarks);
Expand Down
64 changes: 46 additions & 18 deletions src/neo/puzzle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,29 @@ 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 TrackingCandidateCountOfGrid {
fn candidate_cnt_of_grid(&self, r: usize, c: usize) -> i8;
fn candidate_cnt_of_grid_in_row(&self, r: usize, c: usize) -> i8;
fn candidate_cnt_of_grid_in_col(&self, c: usize, r: usize) -> i8;
fn candidate_cnt_of_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;
pub trait TrackingGridCountOfCandidate {
fn grid_cnt_of_candidate_in_row(&self, r: usize, num: i8) -> i8;
fn grid_cnt_of_candidate_in_col(&self, c: usize, num: i8) -> i8;
fn grid_cnt_of_candidate_in_blk(&self, b: usize, num: i8) -> i8;
}

pub trait Fillable {
fn fill_grid(&mut self, r: usize, c: usize, num: i8);
fn unfill_grid(&mut self, r: usize, c: usize);
}

pub trait CandidatesSettable {
fn remove_candidate_of_grid(&mut self, r: usize, c: usize, to_remove: i8);
fn add_candidate_of_grid(&mut self, r: usize, c: usize, to_add: i8);
}

pub struct SudokuPuzzleSimple {
board: [[i8; 9]; 9], // 棋盘
row: [[bool; 10]; 9], // row[r][num] = 第r行是否存在数num
Expand Down Expand Up @@ -115,30 +120,30 @@ impl TrackingCandidates for SudokuPuzzleFull {
}
}

impl TrackingCandidateCountForGrid for SudokuPuzzleFull {
fn candidate_cnt_for_grid(&self, r: usize, c: usize) -> i8 {
impl TrackingCandidateCountOfGrid for SudokuPuzzleFull {
fn candidate_cnt_of_grid(&self, r: usize, c: usize) -> i8 {
self.candidate_cnt[r][c]
}
fn candidate_cnt_for_grid_in_row(&self, r: usize, c: usize) -> i8 {
fn candidate_cnt_of_grid_in_row(&self, r: usize, c: usize) -> i8 {
self.candidate_cnt[r][c]
}
fn candidate_cnt_for_grid_in_col(&self, c: usize, r: usize) -> i8 {
fn candidate_cnt_of_grid_in_col(&self, c: usize, r: usize) -> i8 {
self.candidate_cnt[r][c]
}
fn candidate_cnt_for_grid_in_blk(&self, b: usize, bidx: usize) -> i8 {
fn candidate_cnt_of_grid_in_blk(&self, b: usize, bidx: usize) -> i8 {
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 {
impl TrackingGridCountOfCandidate for SudokuPuzzleFull {
fn grid_cnt_of_candidate_in_row(&self, r: usize, num: i8) -> i8 {
self.grid_cnt_for_candidate_in_row[r][num as usize]
}
fn grid_cnt_for_candidate_in_col(&self, c: usize, num: i8) -> i8 {
fn grid_cnt_of_candidate_in_col(&self, c: usize, num: i8) -> i8 {
self.grid_cnt_for_candidate_in_col[c][num as usize]
}
fn grid_cnt_for_candidate_in_blk(&self, b: usize, num: i8) -> i8 {
fn grid_cnt_of_candidate_in_blk(&self, b: usize, num: i8) -> i8 {
self.grid_cnt_for_candidate_in_blk[b][num as usize]
}
}
Expand Down Expand Up @@ -225,6 +230,29 @@ impl Fillable for SudokuPuzzleFull {
}
}

impl CandidatesSettable for SudokuPuzzleFull {
fn remove_candidate_of_grid(&mut self, r: usize, c: usize, to_remove: i8) {
let to_remove = to_remove as usize;
let b = coord_2_block(r, c);
let is_candidate = self.candidates[r][c][to_remove];
self.candidate_cnt[r][c] -= is_candidate as i8;
self.grid_cnt_for_candidate_in_row[r][to_remove] -= is_candidate as i8;
self.grid_cnt_for_candidate_in_col[c][to_remove] -= is_candidate as i8;
self.grid_cnt_for_candidate_in_blk[b][to_remove] -= is_candidate as i8;
self.candidates[r][c][to_remove] = false;
}
fn add_candidate_of_grid(&mut self, r: usize, c: usize, to_add: i8) {
let to_add = to_add as usize;
let b = coord_2_block(r, c);
let is_candidate = self.candidates[r][c][to_add];
self.candidate_cnt[r][c] += !is_candidate as i8;
self.grid_cnt_for_candidate_in_row[r][to_add] += !is_candidate as i8;
self.grid_cnt_for_candidate_in_col[c][to_add] += !is_candidate as i8;
self.grid_cnt_for_candidate_in_blk[b][to_add] += !is_candidate as i8;
self.candidates[r][c][to_add] = true;
}
}

impl Grid for SudokuPuzzleFull {
fn new(puzzle: [[i8; 9]; 9]) -> Self {
let mut res = Self {
Expand Down
194 changes: 177 additions & 17 deletions src/neo/solver.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
use super::puzzle::{Fillable, Grid, TrackingCandidates};
use std::vec;

use super::{
puzzle::{
CandidatesSettable, Fillable, Grid, TrackingCandidateCountOfGrid, TrackingCandidates,
TrackingGridCountOfCandidate,
},
techniques::{
hidden_pair_blk, hidden_pair_col, hidden_pair_row, hidden_single_blk, hidden_single_col,
hidden_single_row, naked_single,
},
};
use rand::prelude::*;

pub trait Solver {
Expand All @@ -8,6 +19,21 @@ pub trait Solver {
fn have_unique_solution(&mut self) -> bool;
}

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

pub struct StochasticSolver<T>
where
T: Grid + Fillable + TrackingCandidates,
Expand All @@ -26,23 +52,8 @@ where
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);
let coord = next_blank(r, c, &self.puzzle);
if coord.is_none() {
self.solution_cnt += 1;
return true;
Expand Down Expand Up @@ -101,3 +112,152 @@ where
self.solution_cnt == 1
}
}

pub struct TechniquesSolver<T>
where
T: Grid
+ Fillable
+ CandidatesSettable
+ TrackingCandidates
+ TrackingCandidateCountOfGrid
+ TrackingGridCountOfCandidate,
{
puzzle_arr: [[i8; 9]; 9],
puzzle: T,
solution_cnt: u32,
depth: u32,
}

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

fn search(&mut self, solution_cnt_needed: u32) -> bool {
if self.puzzle.board().iter().flatten().all(|v| *v > 0) {
self.solution_cnt += 1;
return solution_cnt_needed <= self.solution_cnt;
}

let step = hidden_single_row(&self.puzzle).unwrap_or(
hidden_single_col(&self.puzzle).unwrap_or(
hidden_single_blk(&self.puzzle)
.unwrap_or(naked_single(&self.puzzle).unwrap_or((0, 0, 0))),
),
);
// 可以通过 hidden single 或 naked single 确定下一步填的数字
if step.2 > 0 {
let (r, c, num) = step;
self.puzzle.fill_grid(r, c, num);
if self.search(solution_cnt_needed) {
return true;
}
self.puzzle.unfill_grid(r, c);
return false;
}

let ((r1, c1), rem1, (r2, c2), rem2, num1, _) =
hidden_pair_row(&self.puzzle).unwrap_or(hidden_pair_col(&self.puzzle).unwrap_or(
hidden_pair_blk(&self.puzzle).unwrap_or(((0, 0), vec![], (0, 0), vec![], 0, 0)),
));
// 可以通过 hidden pair 删除一些候选数字
if num1 > 0 {
self.depth += 1;
for num in &rem1 {
self.puzzle.remove_candidate_of_grid(r1, c1, *num);
}
for num in &rem2 {
self.puzzle.remove_candidate_of_grid(r2, c2, *num);
}
if self.search(solution_cnt_needed) {
return true;
}
for num in &rem1 {
self.puzzle.add_candidate_of_grid(r1, c1, *num);
}
for num in &rem2 {
self.puzzle.add_candidate_of_grid(r2, c2, *num);
}
return false;
}

// 实在不行,找一个候选数字最少的空随便猜一个填上
let mut min_candidate_cnt = 10;
let mut grid = (0, 0);
'outer: for r in 0..9 {
for c in 0..9 {
if self.puzzle.is_grid_empty(r, c) {
if self.puzzle.candidate_cnt_of_grid(r, c) == 2 {
grid = (r, c);
break 'outer;
}
if self.puzzle.candidate_cnt_of_grid(r, c) < min_candidate_cnt {
grid = (r, c);
min_candidate_cnt = self.puzzle.candidate_cnt_of_grid(r, c);
}
}
}
}
let (r, c) = grid;
for num in 1..=9 {
if self.puzzle.is_candidate_of(r, c, num) {
self.puzzle.fill_grid(r, c, num);
if self.search(solution_cnt_needed) {
return true;
}
self.puzzle.unfill_grid(r, c);
}
}

false
}
}

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

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

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

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

0 comments on commit b1f139a

Please sign in to comment.