1use crate::bitset::Set;
2use crate::board::Candidate;
3use crate::board::*;
4use crate::helper::{CellArray, DigitArray, HouseArray, Unsolvable};
5use crate::strategy::{
6 deduction::{Deduction, Deductions},
7 strategies::*,
8};
9use crate::Sudoku;
10
11type EliminationsRange = std::ops::Range<usize>;
12type _Deduction = Deduction<EliminationsRange>;
13
14#[derive(Debug, Clone)]
38pub struct StrategySolver {
39 pub(crate) deductions: Vec<_Deduction>,
40 pub(crate) deduced_entries: Vec<Candidate>,
41 pub(crate) eliminated_entries: Vec<Candidate>,
42 pub(crate) n_solved: u8, pub(crate) hidden_singles_last_house: u8,
46
47 #[allow(unused)]
53 pub(crate) clues: Option<Sudoku>,
54 pub(crate) grid: State<Sudoku>,
58 pub(crate) cell_poss_digits: State<CellArray<Set<Digit>>>,
61 pub(crate) house_solved_digits: State<HouseArray<Set<Digit>>>,
63 pub(crate) house_poss_positions: State<HouseArray<DigitArray<Set<Position<House>>>>>,
65}
66
67impl StrategySolver {
68 fn empty() -> StrategySolver {
69 StrategySolver {
70 deductions: vec![],
71 deduced_entries: vec![],
72 eliminated_entries: vec![],
73 n_solved: 0,
74 hidden_singles_last_house: 0,
75 clues: None,
76 grid: State::from(Sudoku([0; 81])),
77 cell_poss_digits: State::from(CellArray([Set::ALL; 81])),
78 house_solved_digits: State::from(HouseArray([Set::NONE; 27])),
79 house_poss_positions: State::from(HouseArray([DigitArray([Set::ALL; 9]); 27])),
80 }
81 }
82
83 pub fn from_sudoku(sudoku: Sudoku) -> StrategySolver {
85 let deduced_entries = sudoku
86 .iter()
87 .enumerate()
88 .filter_map(|(cell, opt_num)| opt_num.map(|digit| Candidate::new(cell as u8, digit)))
89 .collect();
90
91 StrategySolver {
92 deduced_entries,
93 ..StrategySolver::empty()
94 }
95 }
96
97 pub fn from_sudoku_and_clues(sudoku: Sudoku, clues: Sudoku) -> StrategySolver {
100 StrategySolver {
101 clues: Some(clues),
102 ..StrategySolver::from_sudoku(sudoku)
103 }
104 }
105
106 pub fn from_grid_state(grid_state: [CellState; 81]) -> StrategySolver {
111 let mut entries = vec![];
112 let mut eliminated_candidates = vec![];
113
114 for (cell, &cell_state) in Cell::all().zip(grid_state.iter()) {
115 match cell_state {
116 CellState::Digit(digit) => entries.push(Candidate { cell, digit }),
117 CellState::Candidates(cands) => {
118 for digit in !cands {
119 eliminated_candidates.push(Candidate { cell, digit });
120 }
121 }
122 }
123 }
124
125 StrategySolver {
126 deduced_entries: entries,
127 eliminated_entries: eliminated_candidates,
128 ..StrategySolver::empty()
129 }
130 }
131
132 #[allow(unused)] pub(crate) fn from_grid_state_str(grid_state: &str) -> StrategySolver {
137 let mut _grid_state = [CellState::Candidates(Set::NONE); 81];
138 let entries = grid_state
139 .lines()
140 .flat_map(str::split_whitespace)
141 .filter(|&entry| entry == "_" || entry.parse::<u32>().is_ok());
142
143 for (cell_state, entry) in _grid_state.iter_mut().zip(entries) {
144 let candidates = entry
145 .as_bytes()
146 .iter()
147 .map(|byte| byte - b'0')
148 .filter_map(Digit::new_checked)
150 .fold(Set::NONE, std::ops::BitOr::bitor);
151 let state = match candidates.unique().unwrap_or(None) {
152 Some(digit) => CellState::Digit(digit),
153 None => CellState::Candidates(candidates),
154 };
155 *cell_state = state;
156 }
157
158 Self::from_grid_state(_grid_state)
159 }
160
161 pub fn to_sudoku(&mut self) -> Sudoku {
163 self.update_grid();
164 self.grid.state
165 }
166
167 pub fn grid_state(&self) -> [CellState; 81] {
169 let mut solver = self.clone();
171
172 let mut grid = [CellState::Candidates(Set::NONE); 81];
173
174 solver.update_grid();
175 let _ = solver._update_cell_poss_house_solved(false, false);
176
177 for (cell, &digits) in solver.cell_poss_digits.state.iter().enumerate() {
178 grid[cell] = CellState::Candidates(digits);
179 }
180 for (cell, &digit) in solver
181 .grid
182 .state
183 .0
184 .iter()
185 .enumerate()
186 .filter(|(_, &digit)| digit != 0)
187 {
188 grid[cell] = CellState::Digit(Digit::new(digit));
189 }
190 grid
191 }
192
193 pub fn cell_state(&mut self, cell: Cell) -> CellState {
195 self.update_grid();
196 let _ = self._update_cell_poss_house_solved(false, false);
197
198 let digit = self.grid.state.0[cell.as_index()];
199 if digit != 0 {
200 CellState::Digit(Digit::new(digit))
201 } else {
202 let digits = self.cell_poss_digits.state[cell];
203 CellState::Candidates(digits)
204 }
205 }
206
207 #[allow(clippy::result_unit_err)] pub fn insert_candidate(&mut self, candidate: Candidate) -> Result<(), ()> {
210 self.update_grid();
211 Self::push_new_candidate(
212 &mut self.grid.state,
213 &mut self.deduced_entries,
214 candidate,
215 &mut self.deductions,
216 Deduction::NakedSingles(candidate),
217 )
218 .map_err(|Unsolvable| ())?;
219 self.deductions.pop();
221
222 Ok(())
223 }
224
225 #[rustfmt::skip]
226 fn into_deductions(self) -> Deductions {
227 let Self { deductions, deduced_entries, eliminated_entries, .. } = self;
228 Deductions { deductions, deduced_entries, eliminated_entries }
229 }
230
231 fn update_grid(&mut self) {
232 for &Candidate { cell, digit } in &self.deduced_entries {
233 self.grid.state.0[cell.as_index()] = digit.get();
234 }
235 }
236
237 #[allow(clippy::result_large_err)] pub fn solve(mut self, strategies: &[Strategy]) -> Result<(Sudoku, Deductions), (Sudoku, Deductions)> {
241 self.try_solve(strategies);
242 self.update_grid();
243 match self.is_solved() {
244 true => Ok((self.grid.state, self.into_deductions())),
245 false => Err((self.grid.state, self.into_deductions())),
246 }
247 }
248
249 fn try_solve(&mut self, strategies: &[Strategy]) -> bool {
252 let (first, rest) = match strategies.split_first() {
254 Some(tup) => tup,
255 None => return false,
257 };
258 let lens = (self.deduced_entries.len(), self.eliminated_entries.len());
259 'outer: loop {
260 if self.is_solved() {
261 break;
262 }
263
264 let n_deductions = self.deduced_entries.len();
265 let n_eliminated = self.eliminated_entries.len();
266 if first.deduce_all(self, true).is_err() {
267 break;
268 };
269 if self.deduced_entries.len() > n_deductions {
270 continue 'outer;
271 }
272
273 for strategy in rest {
274 if strategy.deduce_one(self).is_err() {
275 break;
276 };
277 if self.deduced_entries.len() > n_deductions || self.eliminated_entries.len() > n_eliminated {
278 continue 'outer;
279 }
280 }
281 break;
282 }
283 lens < (self.deduced_entries.len(), self.eliminated_entries.len())
284 }
285
286 pub fn is_solved(&self) -> bool {
288 self.n_solved == 81
289 }
290
291 fn update_cell_poss_house_solved(&mut self) -> Result<(), Unsolvable> {
292 self._update_cell_poss_house_solved(false, true)
293 }
294
295 pub(crate) fn _update_cell_poss_house_solved(
296 &mut self,
297 find_naked_singles: bool,
298 early_return_on_error: bool,
299 ) -> Result<(), Unsolvable> {
300 let new_eliminations;
301 {
302 let (_, le_cp, cell_poss) = self.cell_poss_digits.get_mut();
303 new_eliminations = *le_cp as usize > self.eliminated_entries.len();
304
305 for &candidate in &self.eliminated_entries[*le_cp as _..] {
306 let impossibles = candidate.digit_set();
307
308 let res = Self::remove_impossibilities(
311 &mut self.grid.state,
312 cell_poss,
313 candidate.cell,
314 impossibles,
315 &mut self.deduced_entries,
316 &mut self.deductions,
317 find_naked_singles,
318 );
319 if early_return_on_error {
320 res?;
321 }
322 }
323 *le_cp = self.eliminated_entries.len() as _;
324 }
325
326 self.insert_entries(find_naked_singles, new_eliminations)
327 }
328
329 fn update_house_poss_positions(&mut self) {
330 let (ld, le, house_poss_positions) = self.house_poss_positions.get_mut();
334 for candidate in &self.eliminated_entries[*le as usize..] {
336 let cell = candidate.cell;
337 let row_pos = cell.row_pos();
338 let col_pos = cell.col_pos();
339 let block_pos = cell.block_pos();
340 let digit = candidate.digit;
342
343 house_poss_positions[cell.row()][digit].remove(row_pos.as_set());
344 house_poss_positions[cell.col()][digit].remove(col_pos.as_set());
345 house_poss_positions[cell.block()][digit].remove(block_pos.as_set());
346 }
347 *le = self.eliminated_entries.len() as _;
348
349 for candidate in &self.deduced_entries[*ld as usize..] {
350 let cell = candidate.cell;
351 let digit = candidate.digit;
352
353 for cell in cell.neighbors() {
355 let row_pos = cell.row_pos();
356 let col_pos = cell.col_pos();
357 let block_pos = cell.block_pos();
358 house_poss_positions[cell.row()][digit].remove(row_pos.as_set());
359 house_poss_positions[cell.col()][digit].remove(col_pos.as_set());
360 house_poss_positions[cell.block()][digit].remove(block_pos.as_set());
361 }
362
363 let row = cell.row();
364 let col = cell.col();
365 let block = cell.block();
366 let row_pos = cell.row_pos();
367 let col_pos = cell.col_pos();
368 let block_pos = cell.block_pos();
369
370 for digit in Digit::all() {
372 house_poss_positions[row][digit].remove(row_pos.as_set());
373 house_poss_positions[col][digit].remove(col_pos.as_set());
374 house_poss_positions[block][digit].remove(block_pos.as_set());
375 }
376
377 house_poss_positions[row][digit] = Set::NONE;
379 house_poss_positions[col][digit] = Set::NONE;
380 house_poss_positions[block][digit] = Set::NONE;
381 }
382 *ld = self.deduced_entries.len() as _;
383 }
384
385 #[inline(always)]
386 fn insert_entries(&mut self, find_naked_singles: bool, new_eliminations: bool) -> Result<(), Unsolvable> {
387 assert!(self.cell_poss_digits.next_deduced == self.house_solved_digits.next_deduced);
390
391 if new_eliminations {
392 self.batch_insert_entries(find_naked_singles)?;
395 }
396 loop {
397 match self.deduced_entries.len() - self.cell_poss_digits.next_deduced as usize {
398 0 => break Ok(()),
399 1..=4 => self.insert_entries_singly(find_naked_singles)?,
400 _ => self.batch_insert_entries(find_naked_singles)?,
401 }
402 }
403 }
404
405 fn insert_entries_singly(&mut self, find_naked_singles: bool) -> Result<(), Unsolvable> {
410 let (ld_cp, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
411 let (ld_zs, _, house_solved_digits) = self.house_solved_digits.get_mut();
412
413 loop {
414 if self.deduced_entries.len() <= *ld_cp as usize {
415 break;
416 }
417 let candidate = self.deduced_entries[*ld_cp as usize];
418 *ld_cp += 1;
419 *ld_zs += 1;
420 let candidate_mask = candidate.digit_set();
421 if cell_poss_digits[candidate.cell].is_empty() {
423 continue;
424 }
425
426 if (cell_poss_digits[candidate.cell] & candidate_mask).is_empty() {
428 return Err(Unsolvable);
429 }
430
431 Self::_insert_candidate_cp_zs(
432 candidate,
433 &mut self.n_solved,
434 cell_poss_digits,
435 house_solved_digits,
436 );
437 for cell in candidate.cell.neighbors() {
438 if candidate_mask.overlaps(cell_poss_digits[cell]) {
439 Self::remove_impossibilities(
440 &mut self.grid.state,
441 cell_poss_digits,
442 cell,
443 candidate_mask,
444 &mut self.deduced_entries,
445 &mut self.deductions,
446 find_naked_singles,
447 )?;
448 };
449 }
450
451 if self.deduced_entries.len() - *ld_cp as usize > 4 {
453 return Ok(());
454 }
455 }
456 Ok(())
457 }
458
459 #[inline]
460 fn _insert_candidate_cp_zs(
461 candidate: Candidate,
462 n_solved: &mut u8,
463 cell_poss_digits: &mut CellArray<Set<Digit>>,
464 house_solved_digits: &mut HouseArray<Set<Digit>>,
465 ) {
466 *n_solved += 1;
467 cell_poss_digits[candidate.cell] = Set::NONE;
468 house_solved_digits[candidate.row()] |= candidate.digit_set();
469 house_solved_digits[candidate.col()] |= candidate.digit_set();
470 house_solved_digits[candidate.block()] |= candidate.digit_set();
471 }
472
473 fn batch_insert_entries(&mut self, find_naked_singles: bool) -> Result<(), Unsolvable> {
474 self._batch_insert_entries()?;
475 self._batch_remove_conflicts(find_naked_singles)
476 }
477
478 fn _batch_insert_entries(&mut self) -> Result<(), Unsolvable> {
481 let (ld_cp, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
482 let (ld_zs, _, house_solved_digits) = self.house_solved_digits.get_mut();
483 while self.deduced_entries.len() > *ld_cp as usize {
484 let candidate = self.deduced_entries[*ld_cp as usize];
485 *ld_cp += 1;
486 *ld_zs += 1;
487 if cell_poss_digits[candidate.cell].is_empty() {
489 continue;
490 }
491
492 let candidate_mask = candidate.digit_set();
493
494 if house_solved_digits[candidate.row()].overlaps(candidate_mask)
498 || house_solved_digits[candidate.col()].overlaps(candidate_mask)
499 || house_solved_digits[candidate.block()].overlaps(candidate_mask)
500 {
501 return Err(Unsolvable);
502 }
503
504 Self::_insert_candidate_cp_zs(
505 candidate,
506 &mut self.n_solved,
507 cell_poss_digits,
508 house_solved_digits,
509 );
510 }
511 Ok(())
512 }
513
514 fn _batch_remove_conflicts(&mut self, find_naked_singles: bool) -> Result<(), Unsolvable> {
515 let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
516 let (_, _, house_solved_digits) = self.house_solved_digits.get_mut();
517 for cell in Cell::all() {
519 if cell_poss_digits[cell].is_empty() {
520 continue;
521 }
522 let houses_mask = house_solved_digits[cell.row()]
523 | house_solved_digits[cell.col()]
524 | house_solved_digits[cell.block()];
525
526 Self::remove_impossibilities(
527 &mut self.grid.state,
528 cell_poss_digits,
529 cell,
530 houses_mask,
531 &mut self.deduced_entries,
532 &mut self.deductions,
533 find_naked_singles,
534 )?;
535 }
536 Ok(())
537 }
538
539 fn update_for_grid_state_str(&mut self) {
540 self.try_solve(&[Strategy::NakedSingles]);
543
544 self._batch_remove_conflicts_no_check();
550 }
551
552 fn _batch_remove_conflicts_no_check(&mut self) {
555 let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
556 let (_, _, house_solved_digits) = self.house_solved_digits.get_mut();
557 for cell in Cell::all() {
559 if cell_poss_digits[cell].is_empty() {
560 continue;
561 }
562 let houses_mask = house_solved_digits[cell.row()]
563 | house_solved_digits[cell.col()]
564 | house_solved_digits[cell.block()];
565
566 let cell_mask = &mut cell_poss_digits[cell];
567 cell_mask.remove(houses_mask);
568 }
569 }
570
571 fn remove_impossibilities(
574 sudoku: &mut Sudoku,
575 cell_poss_digits: &mut CellArray<Set<Digit>>,
576 cell: Cell,
577 impossible: Set<Digit>,
578 deduced_entries: &mut Vec<Candidate>,
579 deductions: &mut Vec<_Deduction>,
580 find_naked_singles: bool,
581 ) -> Result<(), Unsolvable> {
582 let cell_mask = &mut cell_poss_digits[cell];
583 cell_mask.remove(impossible);
584
585 if find_naked_singles {
586 if let Some(digit) = cell_mask.unique()? {
587 let candidate = Candidate { cell, digit };
588 Self::push_new_candidate(
589 sudoku,
590 deduced_entries,
591 candidate,
592 deductions,
593 Deduction::NakedSingles(candidate),
594 )?;
595 }
596 } else if cell_mask.is_empty() {
597 return Err(Unsolvable);
598 }
599 Ok(())
600 }
601
602 fn push_new_candidate(
603 sudoku: &mut Sudoku,
604 deduced_entries: &mut Vec<Candidate>,
605 candidate: Candidate,
606 deductions: &mut Vec<_Deduction>,
607 strategy: _Deduction, ) -> Result<(), Unsolvable> {
609 #[cfg(debug_assertions)]
610 {
611 use self::Deduction::*;
612 match strategy {
613 NakedSingles(..) | HiddenSingles(..) => (),
614 _ => panic!("Internal error: Called push_new_candidate with wrong strategy type"),
615 };
616 }
617
618 let old_num = &mut sudoku.0[candidate.cell.as_index()];
619 match *old_num {
620 n if n == candidate.digit.get() => return Ok(()), 0 => (), _ => return Err(Unsolvable), }
624 *old_num = candidate.digit.get();
625 deduced_entries.push(candidate);
626 deductions.push(strategy);
627 Ok(())
628 }
629
630 fn enter_conflicts(
633 eliminated: &mut Vec<Candidate>,
634 deductions: &mut Vec<Deduction<EliminationsRange>>,
635 conflicts: impl IntoIterator<Item = Candidate>,
636 deduction: impl FnOnce(EliminationsRange) -> Deduction<EliminationsRange>,
637 ) -> bool {
638 let len_before = eliminated.len();
639 eliminated.extend(conflicts);
640 let conflicts_rg = len_before..eliminated.len();
641
642 #[allow(clippy::len_zero)]
645 let has_conflicts = conflicts_rg.len() > 0;
646
647 if has_conflicts {
648 deductions.push(deduction(conflicts_rg));
649 }
650 has_conflicts
651 }
652
653 pub(crate) fn find_naked_singles(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
658 self.update_cell_poss_house_solved()?;
659
660 {
661 let cell_poss_digits = &self.cell_poss_digits.state;
662 let grid = &mut self.grid.state;
663 let deduced_entries = &mut self.deduced_entries;
664 let deductions = &mut self.deductions;
665
666 naked_singles::find_naked_singles(cell_poss_digits, stop_after_first, |candidate| {
667 deduced_entries.push(candidate);
668 Self::push_new_candidate(
669 grid,
670 deduced_entries,
671 candidate,
672 deductions,
673 Deduction::NakedSingles(candidate),
674 )
675 })?;
676 }
677
678 self.update_cell_poss_house_solved()
680 }
681
682 pub(crate) fn find_hidden_singles(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
683 self.update_cell_poss_house_solved()?;
684
685 {
686 let cell_poss_digits = &self.cell_poss_digits.state;
687 let house_solved_digits = &self.house_solved_digits.state;
688 let grid = &mut self.grid.state;
689 let deduced_entries = &mut self.deduced_entries;
690 let deductions = &mut self.deductions;
691
692 hidden_singles::find_hidden_singles(
693 &mut self.hidden_singles_last_house,
694 cell_poss_digits,
695 house_solved_digits,
696 stop_after_first,
697 |candidate, house| {
698 let deduction = Deduction::HiddenSingles(candidate, house.categorize());
699 Self::push_new_candidate(grid, deduced_entries, candidate, deductions, deduction)
700 },
701 )?;
702 }
703
704 self.update_cell_poss_house_solved()
706 }
707
708 pub(crate) fn find_locked_candidates(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
711 self.update_cell_poss_house_solved()?;
712 let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
713 let eliminated_entries = &mut self.eliminated_entries;
714 let deductions = &mut self.deductions;
715
716 locked_candidates::find_locked_candidates(
717 cell_poss_digits,
718 stop_after_first,
719 |miniline, digit, _miniline_cands, neighbors, is_pointing| {
720 let conflicts = neighbors
721 .iter()
722 .flat_map(|neighb| neighb.cells())
725 .flat_map(|cell| {
726 let conflicts = cell_poss_digits[cell] & digit;
727 conflicts.into_iter().map(move |digit| Candidate { cell, digit })
728 });
729
730 let on_locked = |conflicts| Deduction::LockedCandidates {
731 miniline,
732 digit,
733 is_pointing,
734 conflicts,
735 };
736 Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_locked)
737 },
738 )
739 }
740
741 pub(crate) fn find_naked_subsets(
742 &mut self,
743 subset_size: u8,
744 stop_after_first: bool,
745 ) -> Result<(), Unsolvable> {
746 self.update_cell_poss_house_solved()?;
747 let (_, _, cell_poss_digits) = self.cell_poss_digits.get_mut();
748 let house_solved_digits = &mut self.house_solved_digits.state;
749 let eliminated_entries = &mut self.eliminated_entries;
750 let deductions = &mut self.deductions;
751
752 naked_subsets::find_naked_subsets(
753 cell_poss_digits,
754 house_solved_digits,
755 subset_size,
756 stop_after_first,
757 |house, positions, digits| {
758 let conflicts = (!positions)
759 .into_iter()
760 .map(|pos| house.cell_at(pos))
761 .flat_map(|cell| {
762 let conflicts = cell_poss_digits[cell] & digits;
763 conflicts.into_iter().map(move |digit| Candidate { cell, digit })
764 });
765
766 let on_conflict = |conflicts| Deduction::Subsets {
767 house,
768 positions,
769 digits,
770 conflicts,
771 };
772
773 Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
774 },
775 )
776 }
777
778 pub(crate) fn find_hidden_subsets(
779 &mut self,
780 subset_size: u8,
781 stop_after_first: bool,
782 ) -> Result<(), Unsolvable> {
783 self.update_cell_poss_house_solved()?;
784 self.update_house_poss_positions();
785 let house_poss_positions = &self.house_poss_positions.state;
786 let house_solved_digits = &self.house_solved_digits.state;
787 let eliminated_entries = &mut self.eliminated_entries;
788 let deductions = &mut self.deductions;
789
790 hidden_subsets::find_hidden_subsets(
791 house_solved_digits,
792 house_poss_positions,
793 subset_size,
794 stop_after_first,
795 |house, digits, positions| {
796 let house_poss_positions = house_poss_positions[house];
797
798 let conflicts = (!digits).into_iter().flat_map(|digit| {
799 let conflicts = house_poss_positions[digit] & positions;
800 conflicts
801 .into_iter()
802 .map(|pos| house.cell_at(pos))
803 .map(move |cell| Candidate { cell, digit })
804 });
805
806 let on_conflict = |conflicts| Deduction::Subsets {
807 house,
808 digits,
809 positions,
810 conflicts,
811 };
812
813 Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
814 },
815 )
816 }
817
818 pub(crate) fn find_xwings(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
819 self.find_fish(2, stop_after_first)
820 }
821
822 pub(crate) fn find_swordfish(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
823 self.find_fish(3, stop_after_first)
824 }
825
826 pub(crate) fn find_jellyfish(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
827 self.find_fish(4, stop_after_first)
828 }
829
830 fn find_fish(&mut self, target_size: u8, stop_after_first: bool) -> Result<(), Unsolvable> {
831 self.update_house_poss_positions();
832 self.update_cell_poss_house_solved()?;
833
834 let cell_poss_digits = &self.cell_poss_digits.state;
835 let eliminated_entries = &mut self.eliminated_entries;
836 let deductions = &mut self.deductions;
837 let house_poss_positions = &self.house_poss_positions.state;
838
839 basic_fish::find_fish(
840 house_poss_positions,
841 target_size,
842 stop_after_first,
843 |all_lines, digit, lines, positions_in_line| {
844 let conflicts = all_lines
845 .without(lines)
846 .into_iter()
847 .flat_map(|line| positions_in_line.into_iter().map(move |pos| line.cell_at(pos)))
848 .filter(|&cell| cell_poss_digits[cell].contains(digit))
849 .map(|cell| Candidate { cell, digit });
850
851 let on_conflict = |conflicts| Deduction::BasicFish {
852 lines,
853 digit,
854 conflicts,
855 positions: positions_in_line,
856 };
857
858 Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
859 },
860 )
861 }
862
863 pub(crate) fn find_mutant_fish(
864 &mut self,
865 target_size: u8,
866 stop_after_first: bool,
867 ) -> Result<(), Unsolvable> {
868 self.update_house_poss_positions();
869 self.update_cell_poss_house_solved()?;
870
871 let cell_poss_digits = &self.cell_poss_digits.state;
872 let eliminated_entries = &mut self.eliminated_entries;
873 let deductions = &mut self.deductions;
874 let house_poss_positions = &self.house_poss_positions.state;
875
876 mutant_fish::find_mutant_fish(
877 house_poss_positions,
878 target_size,
879 stop_after_first,
880 |digit, candidate_cells, base, cover: Set<House>| {
881 let cover_cells = cover
882 .into_iter()
883 .map(House::cells)
884 .fold(Set::NONE, std::ops::BitOr::bitor);
885
886 let impossible_cells: Set<Cell> = cover_cells ^ candidate_cells;
887
888 let conflicts = impossible_cells
889 .into_iter()
890 .filter(|&cell| cell_poss_digits[cell].contains(digit))
891 .map(|cell| Candidate { cell, digit });
892
893 let on_conflict = |conflicts| Deduction::Fish {
894 digit,
895 base,
896 cover,
897 conflicts,
898 };
899
900 Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
901 },
902 )
903 }
904
905 pub(crate) fn find_xy_wing(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
906 self.update_cell_poss_house_solved()?;
907 let cell_poss_digits = &self.cell_poss_digits.state;
908 let eliminated_entries = &mut self.eliminated_entries;
909 let deductions = &mut self.deductions;
910
911 xy_wing::find_xy_wing(
912 cell_poss_digits,
913 stop_after_first,
914 |(cell_hinge, poss_digits_hinge), [(cell_pincer1, poss_digs1), (cell_pincer2, poss_digs2)]| {
915 let common_digit = (poss_digs1 & poss_digs2).unique().unwrap().unwrap();
917 let common_neighbors = cell_pincer1.neighbors_set() & cell_pincer2.neighbors_set();
918
919 let conflicts = common_neighbors
920 .into_iter()
921 .filter(|&cell| cell_poss_digits[cell].contains(common_digit))
922 .map(|cell| Candidate {
923 cell,
924 digit: common_digit,
925 });
926
927 let on_conflict = |conflicts| Deduction::Wing {
928 hinge: cell_hinge,
929 hinge_digits: poss_digits_hinge,
930 pincers: cell_pincer1.as_set() | cell_pincer2,
931 conflicts,
932 };
933
934 Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
935 },
936 )
937 }
938
939 pub(crate) fn find_xyz_wing(&mut self, stop_after_first: bool) -> Result<(), Unsolvable> {
940 self.update_cell_poss_house_solved()?;
941 let cell_poss_digits = &self.cell_poss_digits.state;
942 let eliminated_entries = &mut self.eliminated_entries;
943 let deductions = &mut self.deductions;
944
945 xyz_wing::find_xyz_wing(
946 cell_poss_digits,
947 stop_after_first,
948 |(cell_hinge, poss_digits_hinge), [(cell_pincer1, poss_digs1), (cell_pincer2, poss_digs2)]| {
949 let common_digit = (poss_digs1 & poss_digs2).unique().unwrap().unwrap();
951 let common_neighbors =
952 cell_hinge.neighbors_set() & cell_pincer1.neighbors_set() & cell_pincer2.neighbors_set();
953
954 assert_eq!(common_neighbors.len(), 2);
955
956 let conflicts = common_neighbors
957 .into_iter()
958 .filter(|&cell| cell_poss_digits[cell].contains(common_digit))
959 .map(|cell| Candidate {
960 cell,
961 digit: common_digit,
962 });
963
964 let on_conflict = |conflicts| Deduction::Wing {
965 hinge: cell_hinge,
966 hinge_digits: poss_digits_hinge,
967 pincers: cell_pincer1.as_set() | cell_pincer2,
968 conflicts,
969 };
970
971 Self::enter_conflicts(eliminated_entries, deductions, conflicts, on_conflict)
972 },
973 )
974 }
975
976 }
1113
1114impl std::fmt::Display for StrategySolver {
1115 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
1116 let mut solver = self.clone();
1117
1118 solver.update_for_grid_state_str();
1119
1120 print_grid_state(
1121 f,
1122 solver.grid_state(),
1123 "┌",
1124 "┐",
1125 "└",
1126 "┘",
1127 "├",
1128 "┤",
1129 "┬",
1130 "┴",
1131 "┼",
1132 "─",
1133 "│",
1134 )
1148 }
1149}
1150
1151#[derive(Debug, Clone)]
1152pub(crate) struct State<T> {
1153 next_deduced: u16,
1154 last_eliminated: u16, state: T,
1156}
1157
1158impl<T> State<T> {
1159 fn from(this: T) -> Self {
1160 State {
1161 next_deduced: 0,
1162 last_eliminated: 0,
1163 state: this,
1164 }
1165 }
1166}
1167
1168impl<T> State<T> {
1169 fn get_mut(&mut self) -> (&mut u16, &mut u16, &mut T) {
1170 let State {
1171 next_deduced: ld,
1172 last_eliminated: le,
1173 state,
1174 } = self;
1175 (ld, le, state)
1176 }
1177}
1178
1179#[cfg(test)]
1180mod test {
1181 use super::*;
1182 fn read_sudokus(sudokus_str: &str) -> Vec<Sudoku> {
1183 sudokus_str
1184 .lines()
1185 .map(|line| Sudoku::from_str_line(line).unwrap_or_else(|err| panic!("{:?}", err)))
1186 .collect()
1187 }
1188
1189 fn strategy_solver_correct_solution<F>(sudokus: Vec<Sudoku>, solved_sudokus: Vec<Sudoku>, solver: F)
1190 where
1191 F: Fn(StrategySolver, &[Strategy]) -> Result<(Sudoku, Deductions), (Sudoku, Deductions)>,
1192 {
1193 let n_sudokus = sudokus.len();
1194 let strategies = Strategy::ALL;
1195 let mut unsolved = vec![];
1196 for (i, (sudoku, solved_sudoku)) in sudokus.into_iter().zip(solved_sudokus).enumerate() {
1197 let cache = StrategySolver::from_sudoku(sudoku);
1198 match solver(cache, strategies) {
1199 Ok((solution, _deductions)) => assert_eq!(solution, solved_sudoku),
1200 Err((part_solved, _deductions)) => unsolved.push((i, sudoku, part_solved, solved_sudoku)),
1201 }
1202 }
1203 if !unsolved.is_empty() {
1204 println!("Could not solve {}/{} sudokus:\n", unsolved.len(), n_sudokus);
1205
1206 for (i, sudoku, part_solution, _solution) in unsolved {
1207 println!(
1208 "\nsudoku nr {}:\n{}\n{}\n{}",
1209 i + 1,
1210 sudoku.to_str_line(),
1211 part_solution.to_str_line(),
1212 _solution.to_str_line()
1213 );
1214 }
1215 panic!();
1216 }
1217 }
1218
1219 #[test]
1220 fn strategy_solver_correct_solution_easy_sudokus() {
1221 let sudokus = read_sudokus(include_str!("../../sudokus/Lines/easy_sudokus.txt"));
1222 let solved_sudokus = read_sudokus(include_str!("../../sudokus/Lines/solved_easy_sudokus.txt"));
1223 strategy_solver_correct_solution(sudokus, solved_sudokus, StrategySolver::solve);
1224 }
1225
1226 #[test]
1227 fn strategy_solver_correct_solution_medium_sudokus() {
1228 let filter_9 = |vec: Vec<_>| {
1230 vec.into_iter()
1231 .enumerate()
1232 .filter(|&(i, _)| i != 8)
1233 .map(|(_, sudoku)| sudoku)
1234 .collect::<Vec<_>>()
1235 };
1236 let sudokus = filter_9(read_sudokus(include_str!(
1237 "../../sudokus/Lines/medium_sudokus.txt"
1238 )));
1239 let solved_sudokus = filter_9(read_sudokus(include_str!(
1240 "../../sudokus/Lines/solved_medium_sudokus.txt"
1241 )));
1242 strategy_solver_correct_solution(sudokus, solved_sudokus, StrategySolver::solve);
1243 }
1244
1245 #[test]
1246 fn roundtrip_grid_state_str() {
1247 let sudokus = read_sudokus(include_str!("../../sudokus/Lines/easy_sudokus.txt"));
1248
1249 for sudoku in sudokus {
1250 let solver = StrategySolver::from_sudoku(sudoku);
1251 let grid_state_string = solver.to_string();
1252 let solver2 = StrategySolver::from_grid_state_str(&grid_state_string);
1253 let grid_state_string2 = solver2.to_string();
1254 if grid_state_string != grid_state_string2 {
1255 panic!("\n{} \n{}", grid_state_string, grid_state_string2);
1256 }
1257 }
1258 }
1259
1260 #[test]
1261 fn grid_state_str_impossible_sudoku() {
1262 let sudoku =
1263 "12345678.........9...............................................................";
1265 let sudoku = Sudoku::from_str_line(sudoku).unwrap();
1266
1267 let solver = StrategySolver::from_sudoku(sudoku);
1268
1269 #[rustfmt::skip]
1270 let expected =
1271"┌──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐
1272│ 1 2 3 │ 4 5 6 │ 7 8 _ │
1273│ 45678 45678 45678 │ 12378 12378 12378 │ 123456 123456 9 │
1274│ 456789 456789 456789 │ 123789 123789 123789 │ 123456 123456 123456 │
1275├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
1276│ 23456789 13456789 12456789 │ 12356789 12346789 12345789 │ 12345689 12345679 12345678 │
1277│ 23456789 13456789 12456789 │ 12356789 12346789 12345789 │ 12345689 12345679 12345678 │
1278│ 23456789 13456789 12456789 │ 12356789 12346789 12345789 │ 12345689 12345679 12345678 │
1279├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
1280│ 23456789 13456789 12456789 │ 12356789 12346789 12345789 │ 12345689 12345679 12345678 │
1281│ 23456789 13456789 12456789 │ 12356789 12346789 12345789 │ 12345689 12345679 12345678 │
1282│ 23456789 13456789 12456789 │ 12356789 12346789 12345789 │ 12345689 12345679 12345678 │
1283└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
1284";
1285
1286 assert_eq!(expected, &solver.to_string());
1287
1288 }
1294}
1295
1296fn print_grid_state(
1297 f: &mut std::fmt::Formatter,
1298 grid_state: [CellState; 81],
1299 upper_left_corner: &str,
1300 upper_right_corner: &str,
1301 lower_left_corner: &str,
1302 lower_right_corner: &str,
1303
1304 left_junction: &str,
1305 right_junction: &str,
1306 upper_junction: &str,
1307 lower_junction: &str,
1308 middle_junction: &str,
1309
1310 horizontal_bar: &str,
1311 vertical_bar: &str,
1312) -> Result<(), std::fmt::Error> {
1313 let mut column_widths = [0; 9];
1317 for (col, col_width) in column_widths.iter_mut().enumerate() {
1318 let max_width = (0..9)
1319 .map(|row| row * 9 + col)
1320 .map(|cell| match grid_state[cell] {
1321 CellState::Digit(_) => 1,
1322 CellState::Candidates(digits) => std::cmp::max(digits.len(), 1),
1323 })
1324 .max()
1325 .unwrap();
1326
1327 *col_width = max_width;
1328 }
1329
1330 let mut lengths = [0; 3];
1331 for stack in 0..3 {
1332 lengths[stack] = column_widths[stack * 3..][..3].iter().sum::<u8>() as usize;
1333 }
1334 _print_separator(
1335 f,
1336 upper_left_corner,
1337 upper_junction,
1338 upper_right_corner,
1339 horizontal_bar,
1340 lengths,
1341 )?;
1342 for band in 0..3 {
1343 for row in 0..3 {
1344 write!(f, "{}", vertical_bar)?;
1345 for stack in 0..3 {
1346 for col in 0..3 {
1347 let full_row = band * 3 + row;
1348 let full_col = stack * 3 + col;
1349 let cell_state = grid_state[full_row * 9 + full_col];
1350 match cell_state {
1351 CellState::Digit(digit) => {
1352 write!(f, " {:<1$} ", digit.get(), column_widths[full_col] as usize)?
1353 }
1354 CellState::Candidates(cands) => {
1355 write!(f, " ")?;
1356 if cands.is_empty() {
1357 write!(f, "_")?;
1358 } else {
1359 for digit in cands {
1360 write!(f, "{}", digit.get())?;
1361 }
1362 }
1363 write!(
1364 f,
1365 "{:1$}",
1366 ' ',
1367 (1 + column_widths[full_col] - std::cmp::max(cands.len(), 1)) as usize
1368 )?;
1369 }
1370 }
1371 }
1372 write!(f, "{}", vertical_bar)?;
1373 }
1374 writeln!(f)?;
1375 }
1376 if band != 2 {
1377 _print_separator(
1378 f,
1379 left_junction,
1380 middle_junction,
1381 right_junction,
1382 horizontal_bar,
1383 lengths,
1384 )?;
1385 }
1386 }
1387 _print_separator(
1388 f,
1389 lower_left_corner,
1390 lower_junction,
1391 lower_right_corner,
1392 horizontal_bar,
1393 lengths,
1394 )
1395}
1396
1397fn _print_separator(
1398 f: &mut std::fmt::Formatter,
1399 left_junction: &str,
1400 middle_junction: &str,
1401 right_junction: &str,
1402 horizontal_bar: &str,
1403 lengths: [usize; 3],
1404) -> Result<(), std::fmt::Error> {
1405 writeln!(
1406 f,
1407 "{left}{line0}{middle}{line1}{middle}{line2}{right}",
1408 line0 = horizontal_bar.repeat(lengths[0] + 6),
1409 line1 = horizontal_bar.repeat(lengths[1] + 6),
1410 line2 = horizontal_bar.repeat(lengths[2] + 6),
1411 left = left_junction,
1412 middle = middle_junction,
1413 right = right_junction,
1414 )
1415}