diff --git a/benches/solver_benches.rs b/benches/solver_benches.rs index 5b0b64a..9ce53a0 100644 --- a/benches/solver_benches.rs +++ b/benches/solver_benches.rs @@ -1,22 +1,21 @@ use criterion::{criterion_group, criterion_main, Criterion}; use sudoku::{ generator::random_sudoku_puzzle, - puzzle::{SudokuPuzzleFull, SudokuPuzzleSimple}, - solver::{Solver, StochasticSolver, TechniquesSolver}, + solver::{advanced::AdvancedSolver, stochastic::StochasticSolver, Solver}, + state::{full_state::FullState, simple_state::SimpleState}, }; fn benchmarks(c: &mut Criterion) { - let puzzle = random_sudoku_puzzle::< - StochasticSolver, - TechniquesSolver, - >(45, 100, 10000); - let mut solver = StochasticSolver::::new(puzzle); + let puzzle = random_sudoku_puzzle::, AdvancedSolver, f32>( + 45, 0.0, 1000.0, + ); + let mut solver = StochasticSolver::::from(puzzle); c.bench_function("StochasticSolver", |b| { b.iter(|| { solver.any_solution(); }) }); - let mut solver = TechniquesSolver::::new(puzzle); + let mut solver = AdvancedSolver::::from(puzzle); c.bench_function("TechniquesSolver", |b| { b.iter(|| { solver.any_solution(); diff --git a/src/bin/example.rs b/src/bin/example.rs index 3be8d2a..a86968d 100644 --- a/src/bin/example.rs +++ b/src/bin/example.rs @@ -1,21 +1,19 @@ use sudoku::{ generator::random_sudoku_puzzle, - puzzle::{Grid, SudokuPuzzleFull, SudokuPuzzleSimple}, - solver::{Grader, Solver, StochasticSolver, TechniquesSolver}, + solver::{advanced::AdvancedSolver, stochastic::StochasticSolver, Grader, Solver}, + state::{full_state::FullState, simple_state::SimpleState}, 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, naked_single, pointing, - }, + }, Grid, }; fn main() { - let board = random_sudoku_puzzle::< - StochasticSolver, - TechniquesSolver, - >(45, 800, 100000); - let puzzle = SudokuPuzzleFull::new(board); - // TODO: print the puzzle - println!(""); + let board = random_sudoku_puzzle::, AdvancedSolver, f32>( + 45, 140.0, 2000.0, + ); + let puzzle = FullState::from(board); + println!("{}", Grid(board)); let res_hidden_single_row = hidden_single_row(&puzzle); let res_hidden_single_col = hidden_single_col(&puzzle); let res_hidden_single_blk = hidden_single_blk(&puzzle); @@ -38,8 +36,7 @@ fn main() { println!("naked pair in col: {:?}", res_naked_pair_col); println!("naked pair in blk: {:?}", res_naked_pair_blk); println!("pointing: {:?}", res_pointing); - let mut solver2 = TechniquesSolver::::new(board); + let mut solver2 = AdvancedSolver::::from(board); solver2.have_unique_solution(); println!("{}", solver2.difficulty()); - println!("{:?}", board); } diff --git a/src/generator.rs b/src/generator.rs index bef5087..6716a7e 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -2,18 +2,19 @@ use rand::random; use super::solver::{Grader, Solver}; -pub fn random_sudoku_puzzle( - min_blank_cnt: i32, // 需要生成的题目最少空格数 - min_difficulty: i32, // 题目最小难度分数 - max_difficulty: i32, // 题目最大难度分数 +pub fn random_sudoku_puzzle( + min_blank_cnt: i32, // 需要生成的题目最少空格数 + min_difficulty: T, // 题目最小难度分数 + max_difficulty: T, // 题目最大难度分数 ) -> [[i8; 9]; 9] where - T1: Solver, - T2: Solver + Grader, + S1: Solver + From<[[i8; 9]; 9]>, + S2: Solver + Grader + From<[[i8; 9]; 9]>, + T: PartialOrd + From { loop { // 生成随机终局 - let mut puzzle = T1::new([[0; 9]; 9]).any_solution().unwrap(); + let mut puzzle = S1::from([[0; 9]; 9]).any_solution().unwrap(); let mut dug = 0; // 已经挖掉的空格数 let mut trace = vec![]; // 挖空历史记录 @@ -24,7 +25,7 @@ where let mut trace_back_cnt = 0; // 回退的次数 let trace_back_cnt_threshold = 12; // 回退次数阈值,回退次数超过此值会尝试重新生成终局 - let mut difficulty = -1; // 搜索函数在此题目上调用的次数 + let mut difficulty: T = 0.into(); // 搜索函数在此题目上调用的次数 while trace_back_cnt < trace_back_cnt_threshold && !(dug >= min_blank_cnt @@ -48,7 +49,7 @@ where } // 挖空后,判断是否有唯一解 - let mut solver = T2::new(puzzle); + let mut solver = S2::from(puzzle); if solver.have_unique_solution() { difficulty = solver.difficulty(); break; diff --git a/src/lib.rs b/src/lib.rs index a32e90a..8b07831 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,153 @@ -pub mod puzzle; +use std::fmt::Display; + +pub mod generator; +pub mod judge; +pub mod solver; +pub mod state; +pub mod techniques; #[cfg(test)] mod test; pub mod utils; -pub mod techniques; -pub mod solver; -pub mod judge; -pub mod generator; \ No newline at end of file + +pub struct Grid(pub [[i8; 9]; 9]); + +impl Display for Grid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let grid = self.0.map(|row| { + row.map(|cell| { + if cell > 0 { + (cell as u8 + 48) as char + } else { + ' ' + } + }) + }); + writeln!(f, "┏━━━┯━━━┯━━━┳━━━┯━━━┯━━━┳━━━┯━━━┯━━━┓")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[0][0], + grid[0][1], + grid[0][2], + grid[0][3], + grid[0][4], + grid[0][5], + grid[0][6], + grid[0][7], + grid[0][8], + )?; + writeln!(f, "┠───┼───┼───╂───┼───┼───╂───┼───┼───┨")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[1][0], + grid[1][1], + grid[1][2], + grid[1][3], + grid[1][4], + grid[1][5], + grid[1][6], + grid[1][7], + grid[1][8], + )?; + writeln!(f, "┠───┼───┼───╂───┼───┼───╂───┼───┼───┨")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[2][0], + grid[2][1], + grid[2][2], + grid[2][3], + grid[2][4], + grid[2][5], + grid[2][6], + grid[2][7], + grid[2][8], + )?; + writeln!(f, "┣━━━┿━━━┿━━━╋━━━┿━━━┿━━━╋━━━┿━━━┿━━━┫")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[3][0], + grid[3][1], + grid[3][2], + grid[3][3], + grid[3][4], + grid[3][5], + grid[3][6], + grid[3][7], + grid[3][8], + )?; + writeln!(f, "┠───┼───┼───╂───┼───┼───╂───┼───┼───┨")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[4][0], + grid[4][1], + grid[4][2], + grid[4][3], + grid[4][4], + grid[4][5], + grid[4][6], + grid[4][7], + grid[4][8], + )?; + writeln!(f, "┠───┼───┼───╂───┼───┼───╂───┼───┼───┨")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[5][0], + grid[5][1], + grid[5][2], + grid[5][3], + grid[5][4], + grid[5][5], + grid[5][6], + grid[5][7], + grid[5][8], + )?; + writeln!(f, "┣━━━┿━━━┿━━━╋━━━┿━━━┿━━━╋━━━┿━━━┿━━━┫")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[6][0], + grid[6][1], + grid[6][2], + grid[6][3], + grid[6][4], + grid[6][5], + grid[6][6], + grid[6][7], + grid[6][8], + )?; + writeln!(f, "┠───┼───┼───╂───┼───┼───╂───┼───┼───┨")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[7][0], + grid[7][1], + grid[7][2], + grid[7][3], + grid[7][4], + grid[7][5], + grid[7][6], + grid[7][7], + grid[7][8], + )?; + writeln!(f, "┠───┼───┼───╂───┼───┼───╂───┼───┼───┨")?; + writeln!( + f, + "┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃ {} │ {} │ {} ┃", + grid[8][0], + grid[8][1], + grid[8][2], + grid[8][3], + grid[8][4], + grid[8][5], + grid[8][6], + grid[8][7], + grid[8][8], + )?; + write!(f, "┗━━━┷━━━┷━━━┻━━━┷━━━┷━━━┻━━━┷━━━┷━━━┛") + } +} diff --git a/src/solver.rs b/src/solver.rs index 34dec23..d62c494 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -1,30 +1,17 @@ -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_pair_blk, naked_pair_col, naked_pair_row, naked_single, pointing, - }, -}; -use rand::prelude::*; +use super::state::State; 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 trait Grader { - fn difficulty(&self) -> i32; +pub trait Grader { + fn difficulty(&self) -> T; } -fn next_blank(mut row: usize, mut col: usize, puzzle: &impl Grid) -> Option<(usize, usize)> { - while row < 9 && !puzzle.is_grid_empty(row, col) { +fn next_blank(mut row: usize, mut col: usize, state: &impl State) -> Option<(usize, usize)> { + while row < 9 && !state.is_grid_empty(row, col) { if col == 8 { col = 0; row += 1; @@ -38,284 +25,6 @@ fn next_blank(mut row: usize, mut col: usize, puzzle: &impl Grid) -> Option<(usi Some((row, col)) } -pub struct StochasticSolver -where - T: Grid + Fillable + TrackingCandidates, -{ - puzzle_arr: [[i8; 9]; 9], - puzzle: T, - solution_cnt: u32, -} - -impl StochasticSolver -where - T: Grid + Fillable + TrackingCandidates, -{ - fn init_search(&mut self) { - self.solution_cnt = 0; - self.puzzle = T::new(self.puzzle_arr); - } - - fn search(&mut self, r: usize, c: usize, solution_cnt_needed: u32) -> bool { - let coord = next_blank(r, c, &self.puzzle); - if coord.is_none() { - self.solution_cnt += 1; - return true; - } - let (r, c) = coord.unwrap(); - - let mut nums: Vec = (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 Solver for StochasticSolver -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 - } -} - -pub struct TechniquesSolver -where - T: Grid - + Fillable - + CandidatesSettable - + TrackingCandidates - + TrackingCandidateCountOfGrid - + TrackingGridCountOfCandidate, -{ - puzzle_arr: [[i8; 9]; 9], - puzzle: T, - solution_cnt: u32, - invoke_cnt: i32, -} - -impl TechniquesSolver -where - T: Grid - + Fillable - + CandidatesSettable - + TrackingCandidates - + TrackingCandidateCountOfGrid - + TrackingGridCountOfCandidate, -{ - fn init_search(&mut self) { - self.solution_cnt = 0; - self.invoke_cnt = 0; - self.puzzle = T::new(self.puzzle_arr); - } - - fn search(&mut self, solution_cnt_needed: u32) -> bool { - self.invoke_cnt += 1; - 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 { - 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 ((_, _), (_, _), num1, rem1, num2, rem2) = - naked_pair_row(&self.puzzle).unwrap_or(naked_pair_col(&self.puzzle).unwrap_or( - naked_pair_blk(&self.puzzle).unwrap_or(((0, 0), (0, 0), 0, vec![], 0, vec![])), - )); - // 如果可以通过 naked pair 删除一些候选数字 - if num1 > 0 { - for (r, c) in &rem1 { - self.puzzle.remove_candidate_of_grid(*r, *c, num1); - } - for (r, c) in &rem2 { - self.puzzle.remove_candidate_of_grid(*r, *c, num2); - } - if self.search(solution_cnt_needed) { - return true; - } - for (r, c) in &rem1 { - self.puzzle.add_candidate_of_grid(*r, *c, num1); - } - for (r, c) in &rem2 { - self.puzzle.add_candidate_of_grid(*r, *c, num2); - } - return false; - } - - let res_pointing = pointing(&self.puzzle); - // 如果可以通过 pointing 删除一些候选数字 - if res_pointing.is_some() { - let (_, num, rems) = res_pointing.unwrap(); - for (r, c) in &rems { - self.puzzle.remove_candidate_of_grid(*r, *c, num); - } - if self.search(solution_cnt_needed) { - return true; - } - for (r, c) in &rems { - self.puzzle.add_candidate_of_grid(*r, *c, 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 Solver for TechniquesSolver -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, - invoke_cnt: 0, - } - } - - 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 - } -} - -impl Grader for TechniquesSolver -where - T: Grid - + Fillable - + CandidatesSettable - + TrackingCandidates - + TrackingCandidateCountOfGrid - + TrackingGridCountOfCandidate, -{ - fn difficulty(&self) -> i32 { - self.invoke_cnt - } -} +pub mod advanced; +pub mod naive; +pub mod stochastic; diff --git a/src/solver/advanced.rs b/src/solver/advanced.rs new file mode 100644 index 0000000..b7e8570 --- /dev/null +++ b/src/solver/advanced.rs @@ -0,0 +1,238 @@ +use crate::{ + state::{ + CandidatesSettable, Fillable, State, TrackingCandidateCountOfGrid, TrackingCandidates, + TrackingGridCountOfCandidate, + }, + 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, naked_single, pointing, + }, +}; + +use super::{Grader, Solver}; + +pub struct AdvancedSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + puzzle: [[i8; 9]; 9], + state: T, + solution_cnt: u32, + tmp_score: f32, + score: f32 +} + +impl AdvancedSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn init_search(&mut self) { + self.solution_cnt = 0; + self.state = T::from(self.puzzle); + } + + fn search(&mut self, solution_cnt_needed: u32) -> bool { + if self.state.board().iter().flatten().all(|v| *v > 0) { + self.solution_cnt += 1; + self.score = self.tmp_score; + return solution_cnt_needed <= self.solution_cnt; + } + + let step = hidden_single_row(&self.state).unwrap_or( + hidden_single_col(&self.state).unwrap_or( + hidden_single_blk(&self.state) + .unwrap_or(naked_single(&self.state).unwrap_or((0, 0, 0))), + ), + ); + // 如果可以通过 hidden single 或 naked single 确定下一步填的数字 + if step.2 > 0 { + let (r, c, num) = step; + self.state.fill_grid(r, c, num); + self.tmp_score += 1.5; + if self.search(solution_cnt_needed) { + return true; + } + self.state.unfill_grid(r, c); + self.tmp_score -= 1.5; + return false; + } + + let ((r1, c1), rem1, (r2, c2), rem2, num1, _) = + hidden_pair_row(&self.state).unwrap_or(hidden_pair_col(&self.state).unwrap_or( + hidden_pair_blk(&self.state).unwrap_or(((0, 0), vec![], (0, 0), vec![], 0, 0)), + )); + // 如果可以通过 hidden pair 删除一些候选数字 + if num1 > 0 { + for num in &rem1 { + self.state.remove_candidate_of_grid(r1, c1, *num); + } + for num in &rem2 { + self.state.remove_candidate_of_grid(r2, c2, *num); + } + self.tmp_score += 2.7; + if self.search(solution_cnt_needed) { + return true; + } + for num in &rem1 { + self.state.add_candidate_of_grid(r1, c1, *num); + } + for num in &rem2 { + self.state.add_candidate_of_grid(r2, c2, *num); + } + self.tmp_score -= 2.7; + return false; + } + + let ((_, _), (_, _), num1, rem1, num2, rem2) = + naked_pair_row(&self.state).unwrap_or(naked_pair_col(&self.state).unwrap_or( + naked_pair_blk(&self.state).unwrap_or(((0, 0), (0, 0), 0, vec![], 0, vec![])), + )); + // 如果可以通过 naked pair 删除一些候选数字 + if num1 > 0 { + for (r, c) in &rem1 { + self.state.remove_candidate_of_grid(*r, *c, num1); + } + for (r, c) in &rem2 { + self.state.remove_candidate_of_grid(*r, *c, num2); + } + self.tmp_score += 3.0; + if self.search(solution_cnt_needed) { + return true; + } + for (r, c) in &rem1 { + self.state.add_candidate_of_grid(*r, *c, num1); + } + for (r, c) in &rem2 { + self.state.add_candidate_of_grid(*r, *c, num2); + } + self.tmp_score -= 3.0; + return false; + } + + let res_pointing = pointing(&self.state); + // 如果可以通过 pointing 删除一些候选数字 + if res_pointing.is_some() { + let (_, num, rems) = res_pointing.unwrap(); + for (r, c) in &rems { + self.state.remove_candidate_of_grid(*r, *c, num); + } + self.tmp_score += 2.2; + if self.search(solution_cnt_needed) { + return true; + } + for (r, c) in &rems { + self.state.add_candidate_of_grid(*r, *c, num); + } + self.tmp_score -= 2.2; + return false; + } + + // TODO: Claiming, Triplet, X-Wing, Swordfish, XY-Wing, XYZ-Wing + + // 实在不行,找一个候选数字最少的空随便猜一个填上 + let mut min_candidate_cnt = 10; + let mut grid = (0, 0); + 'outer: for r in 0..9 { + for c in 0..9 { + if self.state.is_grid_empty(r, c) { + if self.state.candidate_cnt_of_grid(r, c) == 2 { + grid = (r, c); + break 'outer; + } + if self.state.candidate_cnt_of_grid(r, c) < min_candidate_cnt { + grid = (r, c); + min_candidate_cnt = self.state.candidate_cnt_of_grid(r, c); + } + } + } + } + let (r, c) = grid; + for num in 1..=9 { + if self.state.is_candidate_of(r, c, num) { + self.state.fill_grid(r, c, num); + self.tmp_score += 8.0; + if self.search(solution_cnt_needed) { + return true; + } + self.tmp_score -= 8.0; + self.state.unfill_grid(r, c); + } + } + + false + } +} + +impl From<[[i8; 9]; 9]> for AdvancedSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn from(puzzle: [[i8; 9]; 9]) -> Self { + Self { + puzzle, + state: T::from(puzzle), + solution_cnt: 0, + tmp_score: 0.0, + score: 0.0 + } + } +} + +impl Solver for AdvancedSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn any_solution(&mut self) -> Option<[[i8; 9]; 9]> { + self.init_search(); + if self.search(1) { + return Some(self.state.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 + } +} + +impl Grader for AdvancedSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn difficulty(&self) -> f32 { + self.score + } +} diff --git a/src/solver/naive.rs b/src/solver/naive.rs new file mode 100644 index 0000000..b07ebbe --- /dev/null +++ b/src/solver/naive.rs @@ -0,0 +1,128 @@ +use crate::{ + state::{ + CandidatesSettable, Fillable, State, TrackingCandidateCountOfGrid, TrackingCandidates, + TrackingGridCountOfCandidate, + }, + techniques::{hidden_single_blk, hidden_single_col, hidden_single_row}, +}; + +use super::{Grader, Solver}; + +pub struct NaiveSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + puzzle: [[i8; 9]; 9], + state: T, + solution_cnt: u32, + invoke_cnt: i32, +} + +impl NaiveSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn init_search(&mut self) { + self.solution_cnt = 0; + self.invoke_cnt = 0; + self.state = T::from(self.puzzle); + } + + fn search(&mut self, solution_cnt_needed: u32) -> bool { + self.invoke_cnt += 1; + if self.state.board().iter().flatten().all(|v| *v > 0) { + self.solution_cnt += 1; + return solution_cnt_needed <= self.solution_cnt; + } + + let step = hidden_single_row(&self.state).unwrap_or( + hidden_single_col(&self.state) + .unwrap_or(hidden_single_blk(&self.state).unwrap_or((0, 0, 0))), + ); + // 如果可以通过 hidden single 确定下一步填的数字 + if step.2 > 0 { + let (r, c, num) = step; + self.state.fill_grid(r, c, num); + if self.search(solution_cnt_needed) { + return true; + } + self.state.unfill_grid(r, c); + return false; + } + + false + } +} + +impl From<[[i8; 9]; 9]> for NaiveSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn from(puzzle: [[i8; 9]; 9]) -> Self { + Self { + puzzle, + state: T::from(puzzle), + solution_cnt: 0, + invoke_cnt: 0, + } + } +} + +impl Solver for NaiveSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn any_solution(&mut self) -> Option<[[i8; 9]; 9]> { + self.init_search(); + if self.search(1) { + return Some(self.state.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 + } +} + +impl Grader for NaiveSolver +where + T: State + + Fillable + + CandidatesSettable + + TrackingCandidates + + TrackingCandidateCountOfGrid + + TrackingGridCountOfCandidate, +{ + fn difficulty(&self) -> i32 { + self.invoke_cnt + } +} diff --git a/src/solver/stochastic.rs b/src/solver/stochastic.rs new file mode 100644 index 0000000..b97f489 --- /dev/null +++ b/src/solver/stochastic.rs @@ -0,0 +1,89 @@ +use super::{next_blank, Solver}; + +use crate::state::{Fillable, State, TrackingCandidates}; + +use rand::prelude::*; + +pub struct StochasticSolver +where + T: State + Fillable + TrackingCandidates, +{ + puzzle: [[i8; 9]; 9], + state: T, + solution_cnt: u32, +} + +impl StochasticSolver +where + T: State + Fillable + TrackingCandidates, +{ + fn init_search(&mut self) { + self.solution_cnt = 0; + self.state = T::from(self.puzzle); + } + + fn search(&mut self, r: usize, c: usize, solution_cnt_needed: u32) -> bool { + let coord = next_blank(r, c, &self.state); + if coord.is_none() { + self.solution_cnt += 1; + return true; + } + let (r, c) = coord.unwrap(); + + let mut nums: Vec = (1..=9).collect(); + nums.shuffle(&mut rand::thread_rng()); + for num in nums { + if self.state.is_candidate_of(r, c, num) { + self.state.fill_grid(r, c, num); + + if self.search(r, c, solution_cnt_needed) + && solution_cnt_needed <= self.solution_cnt + { + return true; + } + + self.state.unfill_grid(r, c); + } + } + + false + } +} + +impl From<[[i8; 9]; 9]> for StochasticSolver +where + T: State + Fillable + TrackingCandidates, +{ + fn from(puzzle: [[i8; 9]; 9]) -> Self { + Self { + puzzle, + state: T::from(puzzle), + solution_cnt: 0, + } + } +} + +impl Solver for StochasticSolver +where + T: State + Fillable + TrackingCandidates, +{ + fn any_solution(&mut self) -> Option<[[i8; 9]; 9]> { + self.init_search(); + if self.search(0, 0, 1) { + return Some(self.state.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 + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..7a47f65 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,35 @@ +pub trait State: From<[[i8; 9]; 9]> { + 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: State { + fn is_candidate_of(&self, r: usize, c: usize, num: i8) -> bool; +} + +pub trait TrackingCandidateCountOfGrid: State { + 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 TrackingGridCountOfCandidate: State { + 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: State { + fn fill_grid(&mut self, r: usize, c: usize, num: i8); + fn unfill_grid(&mut self, r: usize, c: usize); +} + +pub trait CandidatesSettable: TrackingCandidates { + 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 mod full_state; +pub mod simple_state; diff --git a/src/puzzle.rs b/src/state/full_state.rs similarity index 64% rename from src/puzzle.rs rename to src/state/full_state.rs index 9d3f978..ff20ba2 100644 --- a/src/puzzle.rs +++ b/src/state/full_state.rs @@ -1,64 +1,49 @@ -use super::utils::{block_idx_2_coord, coord_2_block}; +use crate::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 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 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; -} +use super::{ + CandidatesSettable, Fillable, State, TrackingCandidateCountOfGrid, TrackingCandidates, + TrackingGridCountOfCandidate, +}; -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 - col: [[bool; 10]; 9], // 同理 - block: [[bool; 10]; 9], // 同理 +pub struct FullState { + board: [[i8; 9]; 9], + candidates: [[[bool; 10]; 9]; 9], + candidate_cnt: [[i8; 9]; 9], + grid_cnt_for_candidate_in_row: [[i8; 10]; 9], + grid_cnt_for_candidate_in_col: [[i8; 10]; 9], + grid_cnt_for_candidate_in_blk: [[i8; 10]; 9], + history: Vec<( + [[[bool; 10]; 9]; 9], + [[i8; 9]; 9], + [[i8; 10]; 9], + [[i8; 10]; 9], + [[i8; 10]; 9], + )>, } -impl Grid for SudokuPuzzleSimple { - fn new(puzzle: [[i8; 9]; 9]) -> Self { +impl From<[[i8; 9]; 9]> for FullState { + fn from(puzzle: [[i8; 9]; 9]) -> Self { let mut res = Self { - board: puzzle, - row: [[false; 10]; 9], - col: [[false; 10]; 9], - block: [[false; 10]; 9], + board: [[0; 9]; 9], + candidates: [[[true; 10]; 9]; 9], + candidate_cnt: [[9; 9]; 9], + grid_cnt_for_candidate_in_row: [[9; 10]; 9], + grid_cnt_for_candidate_in_col: [[9; 10]; 9], + grid_cnt_for_candidate_in_blk: [[9; 10]; 9], + history: vec![], }; 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; + if puzzle[r][c] > 0 { + res.fill_grid(r, c, puzzle[r][c]); + } } } res } +} +impl State for FullState { fn grid_val(&self, r: usize, c: usize) -> i8 { self.board[r][c] } @@ -72,55 +57,13 @@ impl Grid for SudokuPuzzleSimple { } } -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], - grid_cnt_for_candidate_in_row: [[i8; 10]; 9], - grid_cnt_for_candidate_in_col: [[i8; 10]; 9], - grid_cnt_for_candidate_in_blk: [[i8; 10]; 9], - history: Vec<( - [[[bool; 10]; 9]; 9], - [[i8; 9]; 9], - [[i8; 10]; 9], - [[i8; 10]; 9], - [[i8; 10]; 9], - )>, -} - -impl TrackingCandidates for SudokuPuzzleFull { +impl TrackingCandidates for FullState { fn is_candidate_of(&self, r: usize, c: usize, num: i8) -> bool { self.candidates[r][c][num as usize] } } -impl TrackingCandidateCountOfGrid for SudokuPuzzleFull { +impl TrackingCandidateCountOfGrid for FullState { fn candidate_cnt_of_grid(&self, r: usize, c: usize) -> i8 { self.candidate_cnt[r][c] } @@ -136,7 +79,7 @@ impl TrackingCandidateCountOfGrid for SudokuPuzzleFull { } } -impl TrackingGridCountOfCandidate for SudokuPuzzleFull { +impl TrackingGridCountOfCandidate for FullState { fn grid_cnt_of_candidate_in_row(&self, r: usize, num: i8) -> i8 { self.grid_cnt_for_candidate_in_row[r][num as usize] } @@ -148,7 +91,7 @@ impl TrackingGridCountOfCandidate for SudokuPuzzleFull { } } -impl Fillable for SudokuPuzzleFull { +impl Fillable for FullState { // 在格 (r, c) 处填上 num fn fill_grid(&mut self, r: usize, c: usize, num: i8) { // 记录历史状态 @@ -230,7 +173,7 @@ impl Fillable for SudokuPuzzleFull { } } -impl CandidatesSettable for SudokuPuzzleFull { +impl CandidatesSettable for FullState { 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); @@ -252,37 +195,3 @@ impl CandidatesSettable for SudokuPuzzleFull { self.candidates[r][c][to_add] = true; } } - -impl Grid for SudokuPuzzleFull { - fn new(puzzle: [[i8; 9]; 9]) -> Self { - let mut res = Self { - board: [[0; 9]; 9], - candidates: [[[true; 10]; 9]; 9], - candidate_cnt: [[9; 9]; 9], - grid_cnt_for_candidate_in_row: [[9; 10]; 9], - grid_cnt_for_candidate_in_col: [[9; 10]; 9], - grid_cnt_for_candidate_in_blk: [[9; 10]; 9], - history: vec![], - }; - for r in 0..9 { - for c in 0..9 { - if puzzle[r][c] > 0 { - res.fill_grid(r, c, puzzle[r][c]); - } - } - } - 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 - } -} diff --git a/src/state/simple_state.rs b/src/state/simple_state.rs new file mode 100644 index 0000000..d935012 --- /dev/null +++ b/src/state/simple_state.rs @@ -0,0 +1,69 @@ +use crate::utils::coord_2_block; + +use super::{Fillable, State, TrackingCandidates}; + +pub struct SimpleState { + board: [[i8; 9]; 9], // 棋盘 + row: [[bool; 10]; 9], // row[r][num] = 第r行是否存在数num + col: [[bool; 10]; 9], // 同理 + block: [[bool; 10]; 9], // 同理 +} + +impl From<[[i8; 9]; 9]> for SimpleState { + fn from(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 + } +} + +impl State for SimpleState { + 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 SimpleState { + 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 SimpleState { + 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; + } +} diff --git a/src/techniques.rs b/src/techniques.rs index 991a9ab..176437e 100644 --- a/src/techniques.rs +++ b/src/techniques.rs @@ -1,27 +1,36 @@ use super::{ - puzzle::{ - Grid, TrackingCandidateCountOfGrid, TrackingCandidates, TrackingGridCountOfCandidate, + state::{ + State, TrackingCandidateCountOfGrid, TrackingCandidates, TrackingGridCountOfCandidate, }, utils::{block_idx_2_coord, coord_2_block}, }; +#[derive(PartialEq, Eq, Hash, Debug)] +pub enum Unit { + Row, + Col, + Block, +} + +// TODO: Add the Direct variants for hidden_pair and pointing + fn hidden_single( - puzzle: &T, + state: &T, grid_cnt_for_candidate: F1, coord_transform: F2, ) -> Option<(usize, usize, i8)> where - T: Grid + TrackingCandidates, + T: State + TrackingCandidates, F1: Fn(&T, usize, i8) -> i8, F2: Fn(usize, usize) -> (usize, usize), { for i in 0..9 { for num in 1..=9 { - if grid_cnt_for_candidate(puzzle, i, num) == 1 { + if grid_cnt_for_candidate(state, i, num) == 1 { let j = (0..9) .filter(|j: &usize| { let (r, c) = coord_transform(i, *j); - puzzle.is_grid_empty(r, c) && puzzle.is_candidate_of(r, c, num) + state.is_grid_empty(r, c) && state.is_candidate_of(r, c, num) }) .next() .unwrap(); @@ -34,43 +43,43 @@ where } pub fn hidden_single_row( - puzzle: &(impl Grid + TrackingCandidates + TrackingGridCountOfCandidate), + state: &(impl State + TrackingCandidates + TrackingGridCountOfCandidate), ) -> Option<(usize, usize, i8)> { hidden_single( - puzzle, + state, |p, r, num| p.grid_cnt_of_candidate_in_row(r, num), |r, c| (r, c), ) } pub fn hidden_single_col( - puzzle: &(impl Grid + TrackingCandidates + TrackingGridCountOfCandidate), + state: &(impl State + TrackingCandidates + TrackingGridCountOfCandidate), ) -> Option<(usize, usize, i8)> { hidden_single( - puzzle, + state, |p, c, num| p.grid_cnt_of_candidate_in_col(c, num), |c, r| (r, c), ) } pub fn hidden_single_blk( - puzzle: &(impl Grid + TrackingCandidates + TrackingGridCountOfCandidate), + state: &(impl State + TrackingCandidates + TrackingGridCountOfCandidate), ) -> Option<(usize, usize, i8)> { hidden_single( - puzzle, + state, |p, b, num| p.grid_cnt_of_candidate_in_blk(b, num), block_idx_2_coord, ) } pub fn naked_single( - puzzle: &(impl Grid + TrackingCandidates + TrackingCandidateCountOfGrid), + state: &(impl State + TrackingCandidates + TrackingCandidateCountOfGrid), ) -> Option<(usize, usize, i8)> { for r in 0..9 { for c in 0..9 { - if puzzle.is_grid_empty(r, c) && puzzle.candidate_cnt_of_grid(r, c) == 1 { + if state.is_grid_empty(r, c) && state.candidate_cnt_of_grid(r, c) == 1 { let num = (1..=9) - .filter(|num| puzzle.is_candidate_of(r, c, *num)) + .filter(|num| state.is_candidate_of(r, c, *num)) .next() .unwrap(); return Some((r, c, num)); @@ -81,18 +90,18 @@ pub fn naked_single( } fn hidden_pair( - puzzle: &T, + state: &T, grid_cnt_for_candidate: F1, coord_transform: F2, ) -> Option<((usize, usize), Vec, (usize, usize), Vec, i8, i8)> where - T: Grid + TrackingCandidates, + T: State + TrackingCandidates, F1: Fn(&T, usize, i8) -> i8, F2: Fn(usize, usize) -> (usize, usize), { for i in 0..9 { let nums: Vec = (1..=9) - .filter(|num| grid_cnt_for_candidate(puzzle, i, *num) == 2) + .filter(|num| grid_cnt_for_candidate(state, i, *num) == 2) .collect(); for i1 in 0..nums.len() { for i2 in 0..i1 { @@ -100,22 +109,22 @@ where let num2 = nums[i2]; if (0..9).all(|j| { let (r, c) = coord_transform(i, j); - !puzzle.is_grid_empty(r, c) - || puzzle.is_candidate_of(r, c, num1) == puzzle.is_candidate_of(r, c, num2) + !state.is_grid_empty(r, c) + || state.is_candidate_of(r, c, num1) == state.is_candidate_of(r, c, num2) }) { let mut jiter = (0..9).filter(|j| { let (r, c) = coord_transform(i, *j); - puzzle.is_grid_empty(r, c) && puzzle.is_candidate_of(r, c, nums[i1]) + state.is_grid_empty(r, c) && state.is_candidate_of(r, c, nums[i1]) }); let j1 = jiter.next().unwrap(); let j2 = jiter.next().unwrap(); let (r1, c1) = coord_transform(i, j1); let (r2, c2) = coord_transform(i, j2); let removes_1: Vec = (1..=9) - .filter(|n| *n != num1 && *n != num2 && puzzle.is_candidate_of(r1, c1, *n)) + .filter(|n| *n != num1 && *n != num2 && state.is_candidate_of(r1, c1, *n)) .collect(); let removes_2: Vec = (1..=9) - .filter(|n| *n != num1 && *n != num2 && puzzle.is_candidate_of(r2, c2, *n)) + .filter(|n| *n != num1 && *n != num2 && state.is_candidate_of(r2, c2, *n)) .collect(); if !removes_1.is_empty() || !removes_2.is_empty() { return Some(( @@ -135,37 +144,37 @@ where } pub fn hidden_pair_row( - puzzle: &(impl Grid + TrackingCandidates + TrackingGridCountOfCandidate), + state: &(impl State + TrackingCandidates + TrackingGridCountOfCandidate), ) -> Option<((usize, usize), Vec, (usize, usize), Vec, i8, i8)> { hidden_pair( - puzzle, + state, |p, r, num| p.grid_cnt_of_candidate_in_row(r, num), |r, c| (r, c), ) } pub fn hidden_pair_col( - puzzle: &(impl Grid + TrackingCandidates + TrackingGridCountOfCandidate), + state: &(impl State + TrackingCandidates + TrackingGridCountOfCandidate), ) -> Option<((usize, usize), Vec, (usize, usize), Vec, i8, i8)> { hidden_pair( - puzzle, + state, |p, c, num| p.grid_cnt_of_candidate_in_col(c, num), |c, r| (r, c), ) } pub fn hidden_pair_blk( - puzzle: &(impl Grid + TrackingCandidates + TrackingGridCountOfCandidate), + state: &(impl State + TrackingCandidates + TrackingGridCountOfCandidate), ) -> Option<((usize, usize), Vec, (usize, usize), Vec, i8, i8)> { hidden_pair( - puzzle, + state, |p, b, num| p.grid_cnt_of_candidate_in_blk(b, num), block_idx_2_coord, ) } fn naked_pair( - puzzle: &T, + state: &T, coord_transform: F, ) -> Option<( (usize, usize), @@ -176,14 +185,14 @@ fn naked_pair( Vec<(usize, usize)>, )> where - T: Grid + TrackingCandidates + TrackingCandidateCountOfGrid, + T: State + TrackingCandidates + TrackingCandidateCountOfGrid, F: Fn(usize, usize) -> (usize, usize), { for i in 0..9 { let js: Vec = (0..9) .filter(|j| { let (r, c) = coord_transform(i, *j); - puzzle.is_grid_empty(r, c) && puzzle.candidate_cnt_of_grid(r, c) == 2 + state.is_grid_empty(r, c) && state.candidate_cnt_of_grid(r, c) == 2 }) .collect(); for i1 in 0..js.len() { @@ -193,9 +202,9 @@ where let (r1, c1) = coord_transform(i, j1); let (r2, c2) = coord_transform(i, j2); if (1..=9).all(|num| { - puzzle.is_candidate_of(r1, c1, num) == puzzle.is_candidate_of(r2, c2, num) + state.is_candidate_of(r1, c1, num) == state.is_candidate_of(r2, c2, num) }) { - let mut num_iter = (1..=9).filter(|num| puzzle.is_candidate_of(r1, c1, *num)); + let mut num_iter = (1..=9).filter(|num| state.is_candidate_of(r1, c1, *num)); let num1 = num_iter.next().unwrap(); let num2 = num_iter.next().unwrap(); let removes_1: Vec<(usize, usize)> = (0..9) @@ -203,8 +212,8 @@ where let (r, c) = coord_transform(i, *j); *j != j1 && *j != j2 - && puzzle.is_grid_empty(r, c) - && puzzle.is_candidate_of(r, c, num1) + && state.is_grid_empty(r, c) + && state.is_candidate_of(r, c, num1) }) .map(|j| coord_transform(i, j)) .collect(); @@ -213,8 +222,8 @@ where let (r, c) = coord_transform(i, *j); *j != j1 && *j != j2 - && puzzle.is_grid_empty(r, c) - && puzzle.is_candidate_of(r, c, num2) + && state.is_grid_empty(r, c) + && state.is_candidate_of(r, c, num2) }) .map(|j| coord_transform(i, j)) .collect(); @@ -229,7 +238,7 @@ where } pub fn naked_pair_row( - puzzle: &(impl Grid + TrackingCandidates + TrackingCandidateCountOfGrid), + state: &(impl State + TrackingCandidates + TrackingCandidateCountOfGrid), ) -> Option<( (usize, usize), (usize, usize), @@ -238,11 +247,11 @@ pub fn naked_pair_row( i8, Vec<(usize, usize)>, )> { - naked_pair(puzzle, |r, c| (r, c)) + naked_pair(state, |r, c| (r, c)) } pub fn naked_pair_col( - puzzle: &(impl Grid + TrackingCandidates + TrackingCandidateCountOfGrid), + state: &(impl State + TrackingCandidates + TrackingCandidateCountOfGrid), ) -> Option<( (usize, usize), (usize, usize), @@ -251,11 +260,11 @@ pub fn naked_pair_col( i8, Vec<(usize, usize)>, )> { - naked_pair(puzzle, |c, r| (r, c)) + naked_pair(state, |c, r| (r, c)) } pub fn naked_pair_blk( - puzzle: &(impl Grid + TrackingCandidates + TrackingCandidateCountOfGrid), + state: &(impl State + TrackingCandidates + TrackingCandidateCountOfGrid), ) -> Option<( (usize, usize), (usize, usize), @@ -264,21 +273,21 @@ pub fn naked_pair_blk( i8, Vec<(usize, usize)>, )> { - naked_pair(puzzle, block_idx_2_coord) + naked_pair(state, block_idx_2_coord) } pub fn pointing( - puzzle: &(impl Grid + TrackingCandidates + TrackingGridCountOfCandidate), + state: &(impl State + TrackingCandidates + TrackingGridCountOfCandidate), ) -> Option<(usize, i8, Vec<(usize, usize)>)> { for b in 0..9 { for num in 1..=9 { - let cnt = puzzle.grid_cnt_of_candidate_in_blk(b, num); + let cnt = state.grid_cnt_of_candidate_in_blk(b, num); if cnt < 1 || cnt > 3 { continue; } let mut bidxs = (0..9).filter(|bidx| { let (r, c) = block_idx_2_coord(b, *bidx); - puzzle.is_grid_empty(r, c) && puzzle.is_candidate_of(r, c, num) + state.is_grid_empty(r, c) && state.is_candidate_of(r, c, num) }); let bidx0 = bidxs.next().unwrap(); // 在同一行 @@ -287,8 +296,8 @@ pub fn pointing( let removes: Vec<(usize, usize)> = (0..9) .filter(|c| { coord_2_block(r, *c) != b - && puzzle.is_grid_empty(r, *c) - && puzzle.is_candidate_of(r, *c, num) + && state.is_grid_empty(r, *c) + && state.is_candidate_of(r, *c, num) }) .map(|c| (r, c)) .collect(); @@ -302,8 +311,8 @@ pub fn pointing( let removes: Vec<(usize, usize)> = (0..9) .filter(|r| { coord_2_block(*r, c) != b - && puzzle.is_grid_empty(*r, c) - && puzzle.is_candidate_of(*r, c, num) + && state.is_grid_empty(*r, c) + && state.is_candidate_of(*r, c, num) }) .map(|r| (r, c)) .collect(); diff --git a/src/test.rs b/src/test.rs index 8b0f834..e34556c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,25 +3,20 @@ use rand::random; use crate::{ generator::random_sudoku_puzzle, judge::judge_sudoku, - puzzle::{ - SudokuPuzzleFull, TrackingCandidateCountOfGrid, TrackingCandidates, - TrackingGridCountOfCandidate, + solver::{advanced::AdvancedSolver, stochastic::StochasticSolver, Solver}, + state::{ + full_state::FullState, simple_state::SimpleState, CandidatesSettable, Fillable, State, + TrackingCandidateCountOfGrid, TrackingCandidates, TrackingGridCountOfCandidate, }, - solver::TechniquesSolver, - utils::{block_idx_2_coord, coord_2_block_idx}, -}; - -use super::{ - puzzle::{CandidatesSettable, Fillable, Grid, SudokuPuzzleSimple}, - solver::{Solver, StochasticSolver}, techniques::{ hidden_pair_row, hidden_single_blk, hidden_single_col, hidden_single_row, naked_single, }, + utils::{block_idx_2_coord, coord_2_block_idx}, }; fn random_sudoku_puzzle_normal() -> [[i8; 9]; 9] { - random_sudoku_puzzle::, TechniquesSolver>( - 45, 100, 10000, + random_sudoku_puzzle::, AdvancedSolver, f32>( + 45, 0.0, 1000.0, ) } @@ -29,7 +24,7 @@ fn random_sudoku_puzzle_normal() -> [[i8; 9]; 9] { fn sudoku_puzzle() { for _ in 0..100 { let puzzle = random_sudoku_puzzle_normal(); - let mut puzzle = SudokuPuzzleFull::new(puzzle); + let mut puzzle = FullState::from(puzzle); let mut moves = vec![]; for _ in 0..10 { @@ -141,7 +136,7 @@ fn sudoku_puzzle() { fn techniques_single() { for _ in 0..100 { let puzzle = random_sudoku_puzzle_normal(); - let mut puzzle = SudokuPuzzleFull::new(puzzle); + let mut puzzle = FullState::from(puzzle); let res_hidden_single_row = hidden_single_row(&puzzle); let res_hidden_single_col = hidden_single_col(&puzzle); let res_hidden_single_blk = hidden_single_blk(&puzzle); @@ -170,10 +165,10 @@ fn techniques_pair() { let mut puzzle = random_sudoku_puzzle_normal(); while res_hidden_pair_row.is_none() { puzzle = random_sudoku_puzzle_normal(); - let puzzle = SudokuPuzzleFull::new(puzzle); + let puzzle = FullState::from(puzzle); res_hidden_pair_row = hidden_pair_row(&puzzle); } - let mut puzzle = SudokuPuzzleFull::new(puzzle); + let mut puzzle = FullState::from(puzzle); let ((r1, c1), _, (r2, c2), _, num1, num2) = res_hidden_pair_row.clone().unwrap(); let nums: Vec = (1..=9).filter(|n| *n != num1 && *n != num2).collect(); for num in &nums { @@ -189,8 +184,8 @@ fn techniques_pair() { fn sudoku_solver() { for _ in 0..100 { let puzzle = random_sudoku_puzzle_normal(); - let mut solver1 = StochasticSolver::::new(puzzle); - let mut solver2 = TechniquesSolver::::new(puzzle); + let mut solver1 = StochasticSolver::::from(puzzle); + let mut solver2 = AdvancedSolver::::from(puzzle); assert!(solver1.have_unique_solution()); assert!(solver2.have_unique_solution()); let solution1 = solver1.any_solution().unwrap();