sudoku_variants/solver/strategy/
impls.rs

1//! This module contains all pre-defined strategies provided by this crate. All
2//! of them are re-exported in [crate::solver::strategy], so you should not
3//! have to `use` anything from this module directly.
4
5use crate::constraint::Constraint;
6use crate::solver::strategy::{Strategy, SudokuInfo};
7use crate::util::USizeSet;
8
9use std::collections::HashSet;
10
11/// A [Strategy] which detects naked singles, that is, cells which only have
12/// one possible value, and enters them into the Sudoku.
13///
14/// As a small example, take a look at the following grid:
15///
16/// ```text
17/// ╔═══╤═══╦═══╤═══╗
18/// ║ X │   ║   │ 2 ║
19/// ╟───┼───╫───┼───╢
20/// ║   │ 1 ║   │   ║
21/// ╠═══╪═══╬═══╪═══╣
22/// ║   │   ║   │   ║
23/// ╟───┼───╫───┼───╢
24/// ║ 3 │   ║   │   ║
25/// ╚═══╧═══╩═══╧═══╝
26/// ```
27///
28/// The cell marked with X cannot be a 1 because of the 1 in its block, nor a 2
29/// because of the 2 in its row, and also cannot be a 3 because of the 3 in its
30/// column. Consequently, it can only be a 4. This would be detected by this
31/// strategy.
32#[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/// A [Strategy] which detects situations in which a digit can only go in one
88/// cell of a group.
89///
90/// As a visualization, the cell marked with X in the following example is the
91/// only one in its block that can be a 2 (using classic Sudoku rules).
92///
93/// ```text
94/// ╔═══╤═══╦═══╤═══╗
95/// ║   │   ║   │ 2 ║
96/// ╟───┼───╫───┼───╢
97/// ║ X │ 1 ║   │   ║
98/// ╠═══╪═══╬═══╪═══╣
99/// ║   │   ║   │   ║
100/// ╟───┼───╫───┼───╢
101/// ║   │   ║   │   ║
102/// ╚═══╧═══╩═══╧═══╝
103/// ```
104#[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                // For smaller groups, there is no guarantee that all digits
119                // are present.
120                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/// A [Strategy] which searches groups for tuples, that is, 2 or more cells
153/// that in total have as many options as there are cells in the tuple. It then
154/// excludes all of these options from all cells in the group which are not a
155/// part of the tuple.
156///
157/// As an example, consider the following configuration (with standard Sudoku
158/// rules):
159///
160/// ```text
161/// ╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗
162/// ║ A │ A │ A ║ 4 │ 5 │ 6 ║ 7 │ 8 │ 9 ║
163/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
164/// ║ B │ B │ B ║ 1 │ 2 │ 3 ║ 4 │ 5 │ 6 ║
165/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
166/// ║   │   │ X ║   │   │   ║   │   │   ║
167/// ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣
168/// ║   │   │ 4 ║   │   │   ║   │   │   ║
169/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
170/// ║   │   │ 5 ║   │   │   ║   │   │   ║
171/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
172/// ║   │   │   ║   │   │   ║   │   │   ║
173/// ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣
174/// ║   │   │   ║   │   │   ║   │   │   ║
175/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
176/// ║   │   │   ║   │   │   ║   │   │   ║
177/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
178/// ║   │   │   ║   │   │   ║   │   │   ║
179/// ╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝
180/// ```
181///
182/// Because the first row already contains the digits 4-9, the cells marked
183/// with A must contain the digits 1-3, meaning they are a triple (3-tuple).
184/// Similarly, the cells marked with B must contain the digits 7-9. This
185/// excludes the options 1-3 and 7-9 from the cell marked with X. The 4 and 5
186/// in the third column then fix it to 6.
187///
188/// When creating a tuple strategy using [TupleStrategy::new], the maximum size
189/// of tuples that are considered can be defined.
190#[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    /// Creates a new tuple strategy that considers tuples up to the size
198    /// defined by `max_size_computer`. This closure receives the size of the
199    /// grid and outputs the maximum size of tuples that this strategy shall
200    /// consider.
201    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        // Note: |options| < |cells| can only be the case if the Sudoku is
229        // impossible.
230        // TODO add a shortcut for returning impossible if a tuple with too
231        // many cells is detected
232
233        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/// A [Strategy] which looks for cells with few options (up to a specified
308/// maximum) and tries all of them. It then uses a wrapped strategy to find
309/// deductions in all paths. If any of those deductions hold for all options,
310/// they are stored in the metadata.
311///
312/// As an example, consider the following situation.
313///
314/// ```text
315/// ╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗
316/// ║ 1 │ A │ 2 ║ 3 │ 4 │ 5 ║ 6 │ B │ 7 ║
317/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
318/// ║   │   │   ║   │   │   ║   │   │   ║
319/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
320/// ║   │   │   ║   │   │   ║   │   │   ║
321/// ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣
322/// ║ 2 │ 3 │ C ║   │   │   ║   │ Z │   ║
323/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
324/// ║ 4 │   │ 1 ║   │   │   ║   │   │   ║
325/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
326/// ║ 5 │ 6 │ 7 ║   │   │   ║   │   │   ║
327/// ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣
328/// ║   │   │   ║   │   │   ║   │   │   ║
329/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
330/// ║   │   │   ║   │   │   ║   │   │   ║
331/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
332/// ║   │   │   ║   │   │   ║   │   │   ║
333/// ╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝
334/// ```
335///
336/// In this case, if A is an 8, then B must be a 9 by the [OnlyCellStrategy],
337/// so Z cannot be a 9. If A is a 9, then C must be a 9 by the same strategy,
338/// and consequently Z cannot be a 9 aswell. So, this strategy with an options
339/// bound of at least 2 (since A can be 8 or 9), an [OnlyCellStrategy] as the
340/// continuation strategy, and at least 1 application, can conclude that Z
341/// cannot be a 9.
342#[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    /// Creates a new bounded options backtracking strategy.
362    ///
363    /// # Arguments
364    ///
365    /// * `max_options_computer`: A closure that, given the grid size, computes
366    /// the maximum number of options that may be present in a cell for this
367    /// strategy to consider all of them.
368    /// * `max_applications_computer`: A closure that, given the grid size,
369    /// computes the maximum number of times the continuation strategy may be
370    /// applied for each considered option before no further inference is done.
371    /// If no limit is desired, this may return `None`.
372    /// * `continuation_strategy`: The [Strategy] with which each considered
373    /// option is developed to find any inferences.
374    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
385/// Applies `continuation_strategy` to `sudoku_info` until the fixed point is
386/// reached, but at most `max_applications` times, if it is given.
387fn 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
404/// Makes deductions in `sudoku_info` under the assumption that one of the
405/// sudoku infos in `results` has to be true - i.e. it contains a complete case
406/// distinction. This function returns `true` if `sudoku_info` changed.
407fn 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/// A [Strategy] which looks for groups in which some number can only occur in
467/// a limited number of cells (up to a specified maximum) and tries all of
468/// them. It then uses a wrapped strategy to find deductions in all paths. If
469/// any of those deductions hold for all options, they are stored in the
470/// metadata.
471///
472/// As an example, consider the following situation:
473///
474/// ```text
475/// ╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗
476/// ║   │   │   ║   │ 5 │   ║   │   │   ║
477/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
478/// ║ 1 │ 2 │ 3 ║   │   │   ║   │   │   ║
479/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
480/// ║ 4 │   │   ║   │   │   ║ X │   │   ║
481/// ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣
482/// ║ 2 │   │   ║   │   │   ║ X │   │   ║
483/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
484/// ║   │   │   ║   │   │ 5 ║   │   │   ║
485/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
486/// ║ 3 │ 1 │ 4 ║   │   │   ║   │   │   ║
487/// ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣
488/// ║   │ Y │ Y ║   │   │   ║   │   │   ║
489/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
490/// ║   │ Y │ Y ║   │   │   ║   │   │   ║
491/// ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢
492/// ║   │ Y │ Y ║   │   │   ║   │   │   ║
493/// ╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝
494/// ```
495///
496/// In this configuration, a bounded cells backtracking strategy without any
497/// wrapped stratey (i.e. a [NoStrategy]) with a maximum of 2 cells to consider
498/// would find that the cells marked with X cannot be a 5. This is because both
499/// in the top-left and the top-central box, there are two places for 5s each,
500/// both in the same rows, thus excluding a 5 from the X-cells. Furthermore, if
501/// an [OnlyCellStrategy] with at least 1 application is used as the
502/// continuation strategy, the bounded cells backtracking strategy would be
503/// able to deduce that fives must always be in columns 2 and 3 in the top-left
504/// and top-central box and thus all cells marked with Y cannot be a 5.
505///
506/// Note that this strategy contains some common strategies with different
507/// names. For example a bounded cells backtracking strategy with a limit of 2
508/// cells and an [OnlyCellStrategy] with 1 application would find X-Wings.
509#[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    /// Creates a new bounded cells backtracking strategy.
529    ///
530    /// # Arguments
531    ///
532    /// * `max_cells_computer`: A closure that, given the grid size, computes
533    /// the maximum number of cells in a group in which a number can be for
534    /// this strategy to consider all of them. 
535    /// * `max_applications_computer`: A closure that, given the grid size,
536    /// computes the maximum number of times the continuation strategy may be
537    /// applied for each considered cell before no further inference is done.
538    /// If no limit is desired, this may return `None`.
539    /// * `continuation_strategy`: The [Strategy] with which each considered
540    /// cell is developed to find any inferences.
541    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            // We must assume that the group contains all numbers for the case
569            // distinction to be valid.
570            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/// A [Strategy] which does nothing. This is to be used in backtracking
617/// strategies to define that no further logic shall be applied after trying an
618/// option.
619#[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
628/// A [Strategy] which uses two strategies by first applying one and then the
629/// other on the output of the first one. If any child changed the state, this
630/// strategy is defined to have changed the state aswell.
631pub struct CompositeStrategy<S1: Strategy, S2: Strategy> {
632    s1: S1,
633    s2: S2
634}
635
636impl<S1: Strategy, S2: Strategy> CompositeStrategy<S1, S2> {
637
638    /// Creates a new composite strategy from the two children strategies.
639    ///
640    /// # Arguments
641    ///
642    /// * `s1`: The strategy which is applied first.
643    /// * `s2`: The strategy which is applied second.
644    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    /// Tests that the `weak_strategy` cannot find deductions that make `test`
684    /// true, but `strong_strategy` can. Also asserts that the resulting Sudoku
685    /// is correct.
686    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        // In this Sudoku, the cell in column 2, row 2 must be a 6, but that
737        // can only be recognized once the options 1, 2, 7, and 8 have been
738        // excluded by the tuple strategy.
739        // Only tuples of size 2 need to be considered.
740
741        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        // This Sudoku is equivalent to the one above, but missing the 3 and 9
762        // in column 2. This means that tuples of size 3 are necessary to
763        // deduce the 6.
764
765        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}