From 7d4cf63bbd3af50df7df073eaed65a3e34462f8d Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Mon, 1 Dec 2025 15:00:35 +0800 Subject: [PATCH] add brute_force --- benches/lib.rs | 1 + src/board/sudoku.rs | 6 ++-- src/strategy/deduction.rs | 4 ++- src/strategy/solver.rs | 70 ++++++++++++++++++++++++++++++++++++-- src/strategy/strategies.rs | 9 +++-- 5 files changed, 80 insertions(+), 10 deletions(-) diff --git a/benches/lib.rs b/benches/lib.rs index df2059d..da19655 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -31,6 +31,7 @@ const ALL_STRATEGIES: &[Strategy] = &[ Strategy::Jellyfish, // 52 Strategy::HiddenQuads, // 54 //Strategy::SinglesChain, + Strategy::BruteForce ]; macro_rules! make_benches { diff --git a/src/board/sudoku.rs b/src/board/sudoku.rs index 2656e94..0e47b6b 100644 --- a/src/board/sudoku.rs +++ b/src/board/sudoku.rs @@ -378,9 +378,9 @@ impl Sudoku { } } - let valid_ending = chars.get(81).map_or(true, |ch| { - matches!(ch, b'\t' | b' ' | b'\r' | b'\n' | b';' | b',') - }); + let valid_ending = chars + .get(81) + .is_none_or(|ch| matches!(ch, b'\t' | b' ' | b'\r' | b'\n' | b';' | b',')); match valid_ending { true => Sudoku::from_bytes(grid).map_err(drop), diff --git a/src/strategy/deduction.rs b/src/strategy/deduction.rs index 32e536d..a85976f 100644 --- a/src/strategy/deduction.rs +++ b/src/strategy/deduction.rs @@ -125,6 +125,7 @@ pub enum Deduction { conflicts: T, }, //SinglesChain(T), + BruteForce(Candidate), } impl Deduction<&'_ [Candidate]> { @@ -191,6 +192,7 @@ impl Deduction<&'_ [Candidate]> { 3 => Strategy::XyzWing, _ => unreachable!(), }, + BruteForce { .. } => Strategy::BruteForce, AvoidableRectangle { .. } => unimplemented!(), } } @@ -234,7 +236,7 @@ impl _Deduction { conflicts } => Wing { hinge, hinge_digits, pincers, conflicts: &eliminated[conflicts] }, - + BruteForce(c) => BruteForce(c), AvoidableRectangle { .. } => unimplemented!(), //SinglesChain(x) => SinglesChain(&eliminated[x]), } diff --git a/src/strategy/solver.rs b/src/strategy/solver.rs index 27f8630..56e2fc0 100644 --- a/src/strategy/solver.rs +++ b/src/strategy/solver.rs @@ -62,6 +62,8 @@ pub struct StrategySolver { pub(crate) house_solved_digits: State>>, // Mask of possible positions for a house and number pub(crate) house_poss_positions: State>>>>, + + pub(crate) solution: Option, } impl StrategySolver { @@ -77,6 +79,7 @@ impl StrategySolver { cell_poss_digits: State::from(CellArray([Set::ALL; 81])), house_solved_digits: State::from(HouseArray([Set::NONE; 27])), house_poss_positions: State::from(HouseArray([DigitArray([Set::ALL; 9]); 27])), + solution: None, } } @@ -90,6 +93,7 @@ impl StrategySolver { StrategySolver { deduced_entries, + solution: sudoku.solution(), ..StrategySolver::empty() } } @@ -122,11 +126,13 @@ impl StrategySolver { } } - StrategySolver { + let mut solver = StrategySolver { deduced_entries: entries, eliminated_entries: eliminated_candidates, ..StrategySolver::empty() - } + }; + solver.solution = solver.to_sudoku().solution(); + solver } /// Construct a new StrategySolver from a printout of cell candidates. @@ -234,6 +240,13 @@ impl StrategySolver { } } + /// Try to solve the sudoku using the all support strategies. Returns a `Result` of the sudoku and a struct containing the series of deductions. + /// If a solution was found, `Ok(..)` is returned, otherwise `Err(..)`. + #[allow(clippy::result_large_err)] // nonsense, Ok and Err are the same size. + pub fn solve_all(self) -> Result<(Sudoku, Deductions), (Sudoku, Deductions)> { + self.solve(Strategy::ALL) + } + /// Try to solve the sudoku using the given `strategies`. Returns a `Result` of the sudoku and a struct containing the series of deductions. /// If a solution was found, `Ok(..)` is returned, otherwise `Err(..)`. #[allow(clippy::result_large_err)] // nonsense, Ok and Err are the same size. @@ -610,7 +623,7 @@ impl StrategySolver { { use self::Deduction::*; match strategy { - NakedSingles(..) | HiddenSingles(..) => (), + NakedSingles(..) | HiddenSingles(..) | BruteForce(..) => (), _ => panic!("Internal error: Called push_new_candidate with wrong strategy type"), }; } @@ -1109,6 +1122,57 @@ impl StrategySolver { Ok(()) } */ + pub(crate) fn find_brute_force(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> { + let solution = match self.solution { + Some(s) => s, + None => return Ok(()), + }; + let cell_states = self.grid_state(); + + let grid = &mut self.grid.state; + let deduced_entries = &mut self.deduced_entries; + let deductions = &mut self.deductions; + + let mut candidates = Vec::new(); + let mut min_len = 255; + let mut candidate_index = 0; + + for (i, cell_state) in cell_states.iter().enumerate() { + if let CellState::Candidates(set) = cell_state { + let candidate = Candidate::new(i as u8, solution.to_bytes()[i]); + if set.len() < min_len { + min_len = set.len(); + candidate_index = candidates.len(); + } + candidates.push(candidate); + } + } + if min_len == 255 { + return Ok(()); + } + if stop_after_first { + deduced_entries.push(candidates[candidate_index]); + Self::push_new_candidate( + grid, + deduced_entries, + candidates[candidate_index], + deductions, + Deduction::BruteForce(candidates[candidate_index]), + )? + } else { + for candidate in candidates { + deduced_entries.push(candidate); + Self::push_new_candidate( + grid, + deduced_entries, + candidate, + deductions, + Deduction::BruteForce(candidate), + )? + } + } + Ok(()) + } } impl std::fmt::Display for StrategySolver { diff --git a/src/strategy/strategies.rs b/src/strategy/strategies.rs index a55e59d..2fd72cf 100644 --- a/src/strategy/strategies.rs +++ b/src/strategy/strategies.rs @@ -40,7 +40,8 @@ pub enum Strategy { MutantSwordfish, MutantJellyfish, AvoidableRectangles, - //SinglesChain, + // SinglesChain, + BruteForce, } impl Strategy { @@ -64,7 +65,8 @@ impl Strategy { Strategy::NakedQuads, // 50 Strategy::Jellyfish, // 52 Strategy::HiddenQuads, // 54 - //Strategy::SinglesChain, + // Strategy::SinglesChain, + Strategy::BruteForce, ]; // is_first_strategy is an optimization hint @@ -96,7 +98,8 @@ impl Strategy { XyzWing => state.find_xyz_wing(stop_after_first), MutantSwordfish => state.find_mutant_fish(3, stop_after_first), MutantJellyfish => state.find_mutant_fish(4, stop_after_first), - //SinglesChain => state.find_singles_chain(stop_after_first), // TODO: Implement non-eager SinglesChain + // SinglesChain => state.find_singles_chain(stop_after_first), // TODO: Implement non-eager SinglesChain + BruteForce => state.find_brute_force(stop_after_first), _ => unimplemented!(), } }