1use crate::constraint::Constraint;
6use crate::solver::strategy::{Strategy, SudokuInfo};
7use crate::util::USizeSet;
8
9use std::collections::HashSet;
10
11#[derive(Clone)]
33pub struct NakedSingleStrategy;
34
35impl Strategy for NakedSingleStrategy {
36
37 fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
38 -> bool {
39 let size = sudoku_info.size();
40 let mut changed = false;
41
42 for row in 0..size {
43 for column in 0..size {
44 let options = sudoku_info.get_options(column, row).unwrap();
45
46 if sudoku_info.get_cell(column, row).unwrap() == None &&
47 options.len() == 1 {
48 let option = options.iter().next().unwrap();
49 sudoku_info.enter_cell_no_update(column, row, option).unwrap();
50 changed = true;
51 }
52 }
53 }
54
55 sudoku_info.invalidate();
56 changed
57 }
58}
59
60#[derive(Clone)]
61enum Location {
62 None,
63 One(usize, usize),
64 Multiple
65}
66
67impl std::fmt::Display for Location {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 match self {
70 Location::None => f.write_str("None"),
71 Location::One(a, b) => f.write_str(format!("One({}, {})", a, b).as_str()),
72 Location::Multiple => f.write_str("Multiple")
73 }
74 }
75}
76
77impl Location {
78 fn union(&self, column: usize, row: usize) -> Location {
79 match self {
80 Location::None => Location::One(column, row),
81 Location::One(_, _) => Location::Multiple,
82 Location::Multiple => Location::Multiple
83 }
84 }
85}
86
87#[derive(Clone)]
105pub struct OnlyCellStrategy;
106
107impl Strategy for OnlyCellStrategy {
108
109 fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
110 -> bool {
111 let size = sudoku_info.size();
112 let grid = sudoku_info.sudoku().grid();
113 let groups = sudoku_info.sudoku().constraint().get_groups(grid);
114 let mut changed = false;
115
116 for group in groups {
117 if group.len() < size {
118 continue;
121 }
122
123 let mut locations = vec![Location::None; size + 1];
124
125 for (column, row) in group {
126 if sudoku_info.get_cell(column, row).unwrap().is_some() {
127 continue;
128 }
129
130 let options = sudoku_info.get_options(column, row).unwrap();
131
132 for option in options.iter() {
133 let location = &locations[option];
134 locations[option] = location.union(column, row);
135 }
136 }
137
138 for (number, location) in locations.into_iter().enumerate() {
139 if let Location::One(column, row) = location {
140 sudoku_info.enqueue_enter_cell(column, row, number)
141 .unwrap();
142 changed = true;
143 }
144 }
145 }
146
147 sudoku_info.invalidate();
148 changed
149 }
150}
151
152#[derive(Clone)]
191pub struct TupleStrategy<F: Fn(usize) -> usize> {
192 max_size_computer: F
193}
194
195impl<F: Fn(usize) -> usize> TupleStrategy<F> {
196
197 pub fn new(max_size_computer: F) -> TupleStrategy<F> {
202 TupleStrategy {
203 max_size_computer
204 }
205 }
206}
207
208#[derive(Clone)]
209struct Tuple {
210 cells: HashSet<(usize, usize)>,
211 options: USizeSet
212}
213
214impl Tuple {
215 fn new(size: usize) -> Tuple {
216 Tuple {
217 cells: HashSet::new(),
218 options: USizeSet::new(1, size).unwrap()
219 }
220 }
221
222 fn add_cell(&mut self, options: &USizeSet, column: usize, row: usize) {
223 self.cells.insert((column, row));
224 self.options |= options;
225 }
226
227 fn is_full(&self) -> bool {
228 let options_len = self.options.len();
234 options_len >= 2 && options_len <= self.cells.len()
235 }
236}
237
238fn find_tuples_rec(sudoku_info: &SudokuInfo<impl Constraint + Clone>,
239 group_rest: &[(usize, usize)], max_size: usize, mut curr_tuple: Tuple,
240 accumulator: &mut Vec<Tuple>) {
241 if curr_tuple.options.len() > max_size {
242 return;
243 }
244
245 if curr_tuple.is_full() {
246 accumulator.push(curr_tuple);
247 return;
248 }
249
250 if let Some((next_column, next_row)) = group_rest.iter().cloned().next() {
251 let next_options =
252 sudoku_info.get_options(next_column, next_row).unwrap();
253 let next_rest = &group_rest[1..];
254
255 if next_options.len() > 1 {
256 find_tuples_rec(sudoku_info, next_rest, max_size,
257 curr_tuple.clone(), accumulator);
258 curr_tuple.add_cell(next_options, next_column, next_row);
259 find_tuples_rec(sudoku_info, next_rest, max_size, curr_tuple,
260 accumulator);
261 }
262 else {
263 find_tuples_rec(sudoku_info, next_rest, max_size, curr_tuple,
264 accumulator);
265 }
266 }
267}
268
269fn find_tuples(sudoku_info: &SudokuInfo<impl Constraint + Clone>,
270 group: &[(usize, usize)], max_size: usize) -> Vec<Tuple> {
271 let mut result = Vec::new();
272 find_tuples_rec(&sudoku_info, group, max_size,
273 Tuple::new(sudoku_info.size()), &mut result);
274 result
275}
276
277impl<F: Fn(usize) -> usize> Strategy for TupleStrategy<F> {
278
279 fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
280 -> bool {
281 let mut changed = false;
282 let grid = sudoku_info.sudoku().grid();
283 let groups = sudoku_info.sudoku().constraint().get_groups(grid);
284 let max_size = (self.max_size_computer)(sudoku_info.size());
285
286 for group in groups {
287 let tuples = find_tuples(&sudoku_info, &group, max_size);
288
289 for tuple in tuples {
290 for (column, row) in group.iter().cloned() {
291 if sudoku_info.get_cell(column, row).unwrap() == None &&
292 !tuple.cells.contains(&(column, row)) {
293 let mut cell_options =
294 sudoku_info.get_options_mut(column, row).unwrap();
295 let before_len = cell_options.len();
296 cell_options -= &tuple.options;
297 changed |= before_len != cell_options.len();
298 }
299 }
300 }
301 }
302
303 changed
304 }
305}
306
307#[derive(Clone)]
343pub struct BoundedOptionsBacktrackingStrategy<FO, FA, S>
344where
345 FO: Fn(usize) -> usize,
346 FA: Fn(usize) -> Option<usize>,
347 S: Strategy
348{
349 max_options_computer: FO,
350 max_applications_computer: FA,
351 continuation_strategy: S
352}
353
354impl<FO, FA, S> BoundedOptionsBacktrackingStrategy<FO, FA, S>
355where
356 FO: Fn(usize) -> usize,
357 FA: Fn(usize) -> Option<usize>,
358 S: Strategy
359{
360
361 pub fn new(max_options_computer: FO, max_applications_computer: FA,
375 continuation_strategy: S)
376 -> BoundedOptionsBacktrackingStrategy<FO, FA, S> {
377 BoundedOptionsBacktrackingStrategy {
378 max_options_computer,
379 max_applications_computer,
380 continuation_strategy
381 }
382 }
383}
384
385fn apply_continuation(max_applications: Option<usize>,
388 continuation_strategy: &impl Strategy,
389 sudoku_info: &mut SudokuInfo<impl Constraint + Clone>) {
390 match max_applications {
391 None => {
392 while continuation_strategy.apply(sudoku_info) { }
393 },
394 Some(max) => {
395 for _ in 0..max {
396 if !continuation_strategy.apply(sudoku_info) {
397 break;
398 }
399 }
400 }
401 }
402}
403
404fn collapse_results<C: Constraint + Clone>(sudoku_info: &mut SudokuInfo<C>,
408 results: Vec<SudokuInfo<C>>) -> bool {
409 if results.is_empty() {
410 return false;
411 }
412
413 let mut results_iter = results.into_iter();
414 let first = results_iter.next().unwrap();
415 let union = results_iter.fold(first,
416 |mut acc, x| {
417 acc.union_assign(&x).unwrap();
418 acc
419 });
420 sudoku_info.intersect_assign(&union).unwrap()
421}
422
423impl<FO, FA, S> Strategy for BoundedOptionsBacktrackingStrategy<FO, FA, S>
424where
425 FO: Fn(usize) -> usize,
426 FA: Fn(usize) -> Option<usize>,
427 S: Strategy
428{
429 fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
430 -> bool {
431 let size = sudoku_info.size();
432 let max_options = (self.max_options_computer)(size);
433 let max_applications = (self.max_applications_computer)(size);
434 let mut changed = false;
435
436 for column in 0..size {
437 for row in 0..size {
438 if sudoku_info.get_cell(column, row).unwrap().is_some() {
439 continue;
440 }
441
442 let options = sudoku_info.get_options(column, row).unwrap();
443
444 if options.len() > max_options {
445 continue;
446 }
447
448 let mut results = Vec::new();
449
450 for option in options.iter() {
451 let mut sudoku_info = sudoku_info.clone();
452 sudoku_info.enter_cell(column, row, option).unwrap();
453 apply_continuation(max_applications,
454 &self.continuation_strategy, &mut sudoku_info);
455 results.push(sudoku_info);
456 }
457
458 changed |= collapse_results(sudoku_info, results);
459 }
460 }
461
462 changed
463 }
464}
465
466#[derive(Clone)]
510pub struct BoundedCellsBacktrackingStrategy<FC, FA, S>
511where
512 FC: Fn(usize) -> usize,
513 FA: Fn(usize) -> Option<usize>,
514 S: Strategy
515{
516 max_cells_computer: FC,
517 max_applications_computer: FA,
518 continuation_strategy: S
519}
520
521impl<FC, FA, S> BoundedCellsBacktrackingStrategy<FC, FA, S>
522where
523 FC: Fn(usize) -> usize,
524 FA: Fn(usize) -> Option<usize>,
525 S: Strategy
526{
527
528 pub fn new(max_cells_computer: FC, max_applications_computer: FA,
542 continuation_strategy: S)
543 -> BoundedCellsBacktrackingStrategy<FC, FA, S> {
544 BoundedCellsBacktrackingStrategy {
545 max_cells_computer,
546 max_applications_computer,
547 continuation_strategy
548 }
549 }
550}
551
552impl<FC, FA, S> Strategy for BoundedCellsBacktrackingStrategy<FC, FA, S>
553where
554 FC: Fn(usize) -> usize,
555 FA: Fn(usize) -> Option<usize>,
556 S: Strategy
557{
558 fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
559 -> bool {
560 let size = sudoku_info.size();
561 let max_cells = (self.max_cells_computer)(size);
562 let max_applications = (self.max_applications_computer)(size);
563 let mut changed = false;
564 let grid = sudoku_info.sudoku().grid();
565 let groups = sudoku_info.sudoku().constraint().get_groups(grid);
566
567 for group in groups {
568 if group.len() < size {
571 continue;
572 }
573
574 let mut number_locations: Vec<Vec<(usize, usize)>> =
575 vec![Vec::new(); size + 1];
576
577 for (column, row) in group {
578 if sudoku_info.get_cell(column, row).unwrap().is_some() {
579 continue;
580 }
581
582 let options = sudoku_info.get_options(column, row).unwrap();
583
584 for option in options.iter() {
585 number_locations[option].push((column, row));
586 }
587 }
588
589 let number_locations_iter = number_locations.into_iter().skip(1);
590
591 for (number, locations) in number_locations_iter.enumerate() {
592 let number = number + 1;
593
594 if locations.is_empty() || locations.len() > max_cells {
595 continue;
596 }
597
598 let mut results = Vec::new();
599
600 for (column, row) in locations {
601 let mut sudoku_info = sudoku_info.clone();
602 sudoku_info.enter_cell(column, row, number).unwrap();
603 apply_continuation(max_applications,
604 &self.continuation_strategy, &mut sudoku_info);
605 results.push(sudoku_info);
606 }
607
608 changed |= collapse_results(sudoku_info, results);
609 }
610 }
611
612 changed
613 }
614}
615
616#[derive(Clone)]
620pub struct NoStrategy;
621
622impl Strategy for NoStrategy {
623 fn apply(&self, _: &mut SudokuInfo<impl Constraint + Clone>) -> bool {
624 false
625 }
626}
627
628pub struct CompositeStrategy<S1: Strategy, S2: Strategy> {
632 s1: S1,
633 s2: S2
634}
635
636impl<S1: Strategy, S2: Strategy> CompositeStrategy<S1, S2> {
637
638 pub fn new(s1: S1, s2: S2) -> CompositeStrategy<S1, S2> {
645 CompositeStrategy {
646 s1,
647 s2
648 }
649 }
650}
651
652impl<S1: Strategy, S2: Strategy> Strategy for CompositeStrategy<S1, S2> {
653 fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
654 -> bool {
655 self.s1.apply(sudoku_info) | self.s2.apply(sudoku_info)
656 }
657}
658
659impl<S1: Strategy + Clone, S2: Strategy + Clone> Clone for CompositeStrategy<S1, S2> {
660 fn clone(&self) -> Self {
661 CompositeStrategy::new(self.s1.clone(), self.s2.clone())
662 }
663}
664
665#[cfg(test)]
666mod tests {
667
668 use super::*;
669
670 use crate::Sudoku;
671 use crate::constraint::DefaultConstraint;
672 use crate::solver::strategy::SudokuInfo;
673
674 fn apply<C: Constraint + Clone, S: Strategy>(strategy: S,
675 sudoku_info: &mut SudokuInfo<C>, apply_once: bool) {
676 while strategy.apply(sudoku_info) {
677 if apply_once {
678 break;
679 }
680 }
681 }
682
683 fn test_strategy_stronger_and_sound<C, W, S>(sudoku: Sudoku<C>,
687 weak_strategy: W, strong_strategy: S, apply_once: bool,
688 test: impl Fn(&SudokuInfo<C>) -> bool)
689 where
690 C: Constraint + Clone,
691 W: Strategy,
692 S: Strategy
693 {
694 let mut sudoku_info = SudokuInfo::from_sudoku(sudoku.clone());
695 apply(weak_strategy, &mut sudoku_info, apply_once);
696 assert!(!test(&sudoku_info));
697
698 let mut sudoku_info = SudokuInfo::from_sudoku(sudoku);
699 apply(strong_strategy, &mut sudoku_info, apply_once);
700 assert!(test(&sudoku_info));
701
702 assert!(sudoku_info.sudoku().is_valid());
703 }
704
705 #[test]
706 fn naked_single_strategy_finds_digit() {
707 let sudoku = Sudoku::parse("2x2;\
708 1, , , ,\
709 , , ,2,\
710 , , , ,\
711 ,3, , ", DefaultConstraint).unwrap();
712 let mut sudoku_info = SudokuInfo::from_sudoku(sudoku);
713
714 assert!(NakedSingleStrategy.apply(&mut sudoku_info));
715 assert_eq!(Some(4), sudoku_info.get_cell(1, 1).unwrap());
716 }
717
718 #[test]
719 fn only_cell_strategy_finds_digits() {
720 let sudoku = Sudoku::parse("2x2;\
721 1, , , ,\
722 , , ,2,\
723 , , , ,\
724 ,3, , ", DefaultConstraint).unwrap();
725 let mut sudoku_info = SudokuInfo::from_sudoku(sudoku);
726
727 assert!(OnlyCellStrategy.apply(&mut sudoku_info));
728 assert_eq!(Some(1), sudoku_info.get_cell(2, 1).unwrap());
729 assert_eq!(Some(1), sudoku_info.get_cell(1, 2).unwrap());
730 assert_eq!(Some(2), sudoku_info.get_cell(1, 0).unwrap());
731 assert_eq!(Some(3), sudoku_info.get_cell(0, 1).unwrap());
732 }
733
734 #[test]
735 fn tuple_strategy_helps_naked_single_strategy() {
736 let sudoku = Sudoku::parse("3x3;\
742 , ,3,4,5,6,7,8,9,\
743 , ,9,1,2,3,4,5,6,\
744 , , , , , , , , ,\
745 , ,4, , , , , , ,\
746 , ,5, , , , , , ,\
747 , , , , , , , , ,\
748 , , , , , , , , ,\
749 , , , , , , , , ,\
750 , , , , , , , , ", DefaultConstraint).unwrap();
751 let weak_strategy = NakedSingleStrategy;
752 let strong_strategy = CompositeStrategy::new(
753 TupleStrategy::new(|_| 2), NakedSingleStrategy);
754
755 test_strategy_stronger_and_sound(sudoku, weak_strategy,
756 strong_strategy, false, |s| s.get_cell(2, 2).unwrap() == Some(6));
757 }
758
759 #[test]
760 fn tuple_strategy_does_not_consider_too_large_tuples() {
761 let sudoku = Sudoku::parse("3x3;\
766 , , ,4,5,6,7,8,9,\
767 , , ,1,2,3,4,5,6,\
768 , , , , , , , , ,\
769 , ,4, , , , , , ,\
770 , ,5, , , , , , ,\
771 , , , , , , , , ,\
772 , , , , , , , , ,\
773 , , , , , , , , ,\
774 , , , , , , , , ", DefaultConstraint).unwrap();
775 let weak_strategy = CompositeStrategy::new(
776 TupleStrategy::new(|_| 2), NakedSingleStrategy);
777 let strong_strategy = CompositeStrategy::new(
778 TupleStrategy::new(|_| 3), NakedSingleStrategy);
779
780 test_strategy_stronger_and_sound(sudoku, weak_strategy,
781 strong_strategy, false, |s| s.get_cell(2, 2).unwrap() == Some(6));
782 }
783
784 #[test]
785 fn bounded_options_backtracking_deduces_impossible_option() {
786 let sudoku = Sudoku::parse("3x3;\
787 1, ,2,3,4,5,6, ,7,\
788 , , , , , , , , ,\
789 , , , , , , , , ,\
790 2,3, , , , , , , ,\
791 4, ,1, , , , , , ,\
792 5,6,7, , , , , , ,\
793 , , , , , , , , ,\
794 , , , , , , , , ,\
795 , , , , , , , , ", DefaultConstraint).unwrap();
796 let strategy =
797 BoundedOptionsBacktrackingStrategy::new(|_| 2, |_| Some(1),
798 OnlyCellStrategy);
799 let mut sudoku_info = SudokuInfo::from_sudoku(sudoku);
800
801 assert!(strategy.apply(&mut sudoku_info));
802 assert!(!sudoku_info.get_options(7, 3).unwrap().contains(8));
803 assert!(!sudoku_info.get_options(7, 3).unwrap().contains(9));
804 }
805
806 fn has_option<C: Constraint + Clone>(sudoku_info: &SudokuInfo<C>,
807 column: usize, row: usize, option: usize) -> bool {
808 sudoku_info.get_options(column, row).unwrap().contains(option)
809 }
810
811 #[test]
812 fn bounded_options_backtracking_respects_application_limit() {
813 let sudoku = Sudoku::parse("3x3;\
814 1, ,2,3,4,5,6, ,7,\
815 , , , , , , , , ,\
816 , , , , , , , , ,\
817 2,1, , , , , , , ,\
818 3, ,6, , , , , , ,\
819 4,5,7, , , , , , ,\
820 , , , , , , , , ,\
821 , , , , , , , , ,\
822 , , , , , , , , ", DefaultConstraint).unwrap();
823 let weak_strategy =
824 BoundedOptionsBacktrackingStrategy::new(|_| 2, |_| Some(1),
825 NakedSingleStrategy);
826 let strong_strategy =
827 BoundedOptionsBacktrackingStrategy::new(|_| 2, |_| Some(2),
828 NakedSingleStrategy);
829 test_strategy_stronger_and_sound(sudoku, weak_strategy,
830 strong_strategy, true, |s|
831 !has_option(s, 7, 3, 9) && !has_option(s, 7, 3, 9))
832 }
833
834 #[test]
835 fn bounded_options_backtracking_respects_option_limit() {
836 let sudoku = Sudoku::parse("3x3;\
837 1, ,2,3, ,4,5, ,6,\
838 , , , , , , , , ,\
839 , , ,7, , ,8, , ,\
840 2, , ,1, , , , , ,\
841 3, ,1,4, ,5, , , ,\
842 4, ,5,2, ,3, , , ,\
843 , , , , , , , , ,\
844 , , , , , , , , ,\
845 , , , , , , , , ", DefaultConstraint).unwrap();
846 let weak_strategy =
847 BoundedOptionsBacktrackingStrategy::new(|_| 2, |_| None,
848 OnlyCellStrategy);
849 let strong_strategy =
850 BoundedOptionsBacktrackingStrategy::new(|_| 3, |_| None,
851 OnlyCellStrategy);
852 test_strategy_stronger_and_sound(sudoku, weak_strategy,
853 strong_strategy, true, |s| !has_option(s, 7, 3, 9));
854 }
855
856 #[test]
857 fn bounded_cells_backtracking_detects_impossible_option() {
858 let sudoku = Sudoku::parse("3x3;\
859 , , , ,5, , , , ,\
860 1,2,3, , , , , , ,\
861 4, , , , , , , , ,\
862 2, , , , , , , , ,\
863 3,1,4, , , , , , ,\
864 , , , , ,5, , , ,\
865 , , , , , , , , ,\
866 , , , , , , , , ,\
867 , , , , , , , , ", DefaultConstraint).unwrap();
868 let strategy =
869 BoundedCellsBacktrackingStrategy::new(|_| 2, |_| Some(0),
870 NoStrategy);
871 let mut sudoku_info = SudokuInfo::from_sudoku(sudoku);
872
873 assert!(strategy.apply(&mut sudoku_info));
874 assert!(!sudoku_info.get_options(6, 2).unwrap().contains(5));
875 assert!(!sudoku_info.get_options(6, 3).unwrap().contains(5));
876 }
877
878 #[test]
879 fn bounded_cells_backtracking_respects_application_limit() {
880 let sudoku = Sudoku::parse("3x3;\
881 , , , ,5, , , , ,\
882 1,2,3, , , , , , ,\
883 4, , , , , , , , ,\
884 2, , , , , , , , ,\
885 3,1,4, , , , , , ,\
886 , , , , ,5, , , ,\
887 , , , , , , , , ,\
888 , , , , , , , , ,\
889 , , , , , , , , ", DefaultConstraint).unwrap();
890 let weak_strategy =
891 BoundedCellsBacktrackingStrategy::new(|_| 2, |_| Some(0),
892 OnlyCellStrategy);
893 let strong_strategy =
894 BoundedCellsBacktrackingStrategy::new(|_| 2, |_| Some(1),
895 OnlyCellStrategy);
896 test_strategy_stronger_and_sound(sudoku, weak_strategy,
897 strong_strategy, true, |s|
898 !has_option(s, 1, 6, 5) && !has_option(s, 2, 6, 5));
899 }
900
901 #[test]
902 fn bounded_cells_backtracking_respects_cell_limit() {
903 let sudoku = Sudoku::parse("3x3;\
904 , , , ,5, , , , ,\
905 1,2,3, , , , , , ,\
906 4, , , , , , , , ,\
907 2,1, , , , , , , ,\
908 3, , , , , , , , ,\
909 , , , , ,5, , , ,\
910 , , , , , , , , ,\
911 , , , , , , , , ,\
912 , , , , , , , , ", DefaultConstraint).unwrap();
913 let weak_strategy =
914 BoundedCellsBacktrackingStrategy::new(|_| 2, |_| Some(1),
915 OnlyCellStrategy);
916 let strong_strategy =
917 BoundedCellsBacktrackingStrategy::new(|_| 3, |_| Some(1),
918 OnlyCellStrategy);
919 test_strategy_stronger_and_sound(sudoku, weak_strategy,
920 strong_strategy, true, |s| !has_option(s, 2, 6, 5));
921 }
922}