sudoku_variants/solver/strategy/
mod.rs

1//! This module is about strategic solving of Sudoku. In contrast to
2//! backtracking, this is often faster, but cannot solve all Sudoku. However,
3//! strategic backtracking is also possible, which still uses backtracking, but
4//! also uses strategies to reduce the search space. This is solely a
5//! performance optimization and offers no functional advantage over pure
6//! backtracking. For more information, view the [StrategicBacktrackingSolver].
7//!
8//! This module contains the definition of the [Strategy] trait, which all
9//! strategies must implement, as well as the [SudokuInfo] struct, which wraps
10//! metadata about Sudoku that can be used by strategies to exchange
11//! information.
12//!
13//! `sudoku-variants` offers a small library of pre-defined strategies you can
14//! use. See the [impls] module for further details.
15//!
16//! # Defining difficulties using strategies
17//!
18//! Besides a performance optimization for backtracking, strategies also have
19//! the use of defining a difficulty level for generated Sudoku. This can be
20//! done by instantiating the [Reducer](crate::generator::Reducer) with a
21//! [StrategicSolver]. The resulting Sudoku is then guaranteed to be solveable
22//! by the provided strategy. As an example consider the following code, which
23//! generates a classic Sudoku that can be solved by solely looking for naked
24//! singles (see [NakedSingleStrategy]).
25//!
26//! ```
27//! use sudoku_variants::constraint::DefaultConstraint;
28//! use sudoku_variants::generator::{Generator, Reducer};
29//! use sudoku_variants::solver::{Solution, Solver};
30//! use sudoku_variants::solver::strategy::{
31//!     NakedSingleStrategy,
32//!     StrategicSolver
33//! };
34//!
35//! // Generate the full Sudoku
36//! let mut generator = Generator::new_default();
37//! let mut sudoku = generator.generate(3, 3, DefaultConstraint).unwrap();
38//! let expected_solution = sudoku.grid().clone();
39//!
40//! // Define the difficulty level by providing a not-so-powerful solver
41//! let solver = StrategicSolver::new(NakedSingleStrategy);
42//!
43//! // Reduce the Sudoku using the solver
44//! let mut reducer = Reducer::new(solver.clone(), rand::thread_rng());
45//! reducer.reduce(&mut sudoku);
46//!
47//! // Test that the solver can in fact solve the Sudoku
48//! let actual_solution = solver.solve(&sudoku);
49//! assert_eq!(Solution::Unique(expected_solution), actual_solution);
50//! ```
51//!
52//! # Implementing a custom strategy
53//!
54//! As an example let's define a strategy that puts a 1 in every cell without
55//! any other option. This is a subset of the [NakedSingleStrategy].
56//!
57//! To do this, we must implement the [Strategy] trait, which only requires the
58//! [Strategy::apply] method. This method gets an instance of a [SudokuInfo]
59//! struct for the Sudoku at hand. We must then implement our logic to make
60//! deductions and if we can find something, that is, we can write in a digit
61//! or remove an option, we can modify the sudoku info. If we changed
62//! something, we must return true, and false otherwise. This indicates to the
63//! solvers whether it is useful to apply this strategy or other strategies
64//! again, since we may find something new.
65//!
66//! In our case, we can implement this method as follows:
67//!
68//! ```
69//! use sudoku_variants::constraint::Constraint;
70//! use sudoku_variants::solver::strategy::{Strategy, SudokuInfo};
71//!
72//! struct NakedOneStrategy;
73//!
74//! impl Strategy for NakedOneStrategy {
75//!     fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
76//!             -> bool {
77//!         let size = sudoku_info.sudoku().grid().size();
78//!         let mut changed = false;
79//!
80//!         // We must iterate over every cell.
81//!         for row in 0..size {
82//!             for column in 0..size {
83//!                 // The SudokuInfo struct stores which digits could go into
84//!                 // each cell. We can get that information with get_options.
85//!                 let options =
86//!                     sudoku_info.get_options(column, row).unwrap();
87//!
88//!                 if options.len() == 1 && options.contains(1) {
89//!                     // Only a 1 can go into this cell! We found something!
90//!                     // We batch all changes using enter_cell_no_update for
91//!                     // performance reasons.
92//!                     sudoku_info.enter_cell_no_update(column, row, 1)
93//!                         .unwrap();
94//!                     changed = true;
95//!                 }
96//!             }
97//!         }
98//!
99//!         changed
100//!     }
101//! }
102//! ```
103
104use crate::Sudoku;
105use crate::constraint::Constraint;
106use crate::error::{SudokuError, SudokuResult};
107use crate::util::USizeSet;
108
109pub mod impls;
110pub mod solvers;
111
112pub use impls::*;
113pub use solvers::*;
114
115/// Enriches a [Sudoku] with additional information about which numbers can go
116/// into the cells. This is analogous to the pencil markings a human player
117/// would make. It is used by [Strategies](Strategy) to communicate the results
118/// of logical reasoning.
119///
120/// This struct already excludes options which violate the Sudoku's constraint,
121/// unless unprocessed changes have been made.
122#[derive(Clone)]
123pub struct SudokuInfo<C: Constraint + Clone> {
124    sudoku: Sudoku<C>,
125    cell_options: Vec<USizeSet>,
126    enqueued_cells: Vec<(usize, usize, usize)>,
127    up_to_date: bool
128}
129
130impl<C: Constraint + Clone> SudokuInfo<C> {
131
132    /// Creates a new Sudok info for a [Sudoku]. The options for all cells that
133    /// are empty in the provided Sudoku are all valid digits, and the options
134    /// for cells which are filled in the Sudoku are only the digit in that
135    /// cell.
136    pub fn from_sudoku(sudoku: Sudoku<C>) -> SudokuInfo<C> {
137        let size = sudoku.grid().size();
138        let mut cell_options = Vec::new();
139
140        for row in 0..size {
141            for column in 0..size {
142                let cell = sudoku.grid().get_cell(column, row).unwrap();
143                let options = match cell {
144                    Some(number) =>
145                        USizeSet::singleton(1, size, number).unwrap(),
146                    None => {
147                        let mut options = USizeSet::new(1, size).unwrap();
148
149                        for option in 1..=size {
150                            let is_valid = sudoku
151                                .is_valid_number(column, row, option)
152                                .unwrap();
153
154                            if is_valid {
155                                options.insert(option).unwrap();
156                            }
157                        }
158
159                        options
160                    }
161                };
162
163                cell_options.push(options);
164            }
165        }
166
167        SudokuInfo {
168            sudoku,
169            cell_options,
170            enqueued_cells: Vec::new(),
171            up_to_date: true
172        }
173    }
174
175    fn verified_index(&self, column: usize, row: usize)
176            -> SudokuResult<usize> {
177        let size = self.size();
178
179        if column >= size || row >= size {
180            Err(SudokuError::OutOfBounds)
181        }
182        else {
183            Ok(crate::index(column, row, size))
184        }
185    }
186
187    /// Gets the content of the cell at the specified position.
188    ///
189    /// This is syntactic sugar for `x.sudoku().grid().get_cell(...)`.
190    ///
191    /// # Arguments
192    ///
193    /// * `column`: The column (x-coordinate) of the desired cell. Must be in
194    /// the range `[0, size[`.
195    /// * `row`: The row (y-coordinate) of the desired cell. Must be in the
196    /// range `[0, size[`.
197    ///
198    /// # Errors
199    ///
200    /// If either `column` or `row` are not in the specified range. In that
201    /// case, `SudokuError::OutOfBounds` is returned.
202    pub fn get_cell(&self, column: usize, row: usize)
203            -> SudokuResult<Option<usize>> {
204        self.sudoku.grid().get_cell(column, row)
205    }
206
207    /// Enqueues a number to be assigned to the content of the cell at the
208    /// specified position on the next update. If the cell is not empty at that
209    /// point, the old number will be overwritten.
210    ///
211    /// In contrast with
212    /// [enter_cell_no_update](SudokuInfo::enter_cell_no_update), this function
213    /// never enters the number into the cell right away, so when querying the
214    /// cell it will still look empty. This is done both for performance
215    /// reasons and to preserve semantics of only applying a strategy once for
216    /// strategies which may process the same cell more than once, which is
217    /// important for the bounded backtracking strategies. To ensure that the
218    /// state of this is up-to-date, i.e. the new cells are entered and the
219    /// options are adapted to accomodate for them, call
220    /// [invalidate](SudokuInfo::invalidate) after you are finished enqueueing
221    /// changes.
222    ///
223    /// # Arguments
224    ///
225    /// * `column`: The column (x-coordinate) of the assigned cell. Must be in
226    /// the range `[0, size[`.
227    /// * `row`: The row (y-coordinate) of the assigned cell. Must be in the
228    /// range `[0, size[`.
229    /// * `number`: The number to assign to the specified cell. Must be in the
230    /// range `[1, size]`.
231    ///
232    /// # Errors
233    ///
234    /// * `SudokuError::OutOfBounds` If either `column` or `row` are not in the
235    /// specified range.
236    /// * `SudokuError::InvalidNumber` If `number` is not in the specified
237    /// range.
238    pub fn enqueue_enter_cell(&mut self, column: usize, row: usize,
239            number: usize) -> SudokuResult<()> {
240        let size = self.sudoku.grid().size();
241
242        if column >= size || row >= size {
243            return Err(SudokuError::InvalidDimensions);
244        }
245
246        if number < 1 || number > size {
247            return Err(SudokuError::InvalidNumber);
248        }
249
250        self.enqueued_cells.push((column, row, number));
251        self.up_to_date = false;
252        Ok(())
253    }
254
255    /// Sets the content of the cell at the specified position to the given
256    /// number. If the cell was not empty, the old number will be overwritten.
257    ///
258    /// In contrast with [enter_cell](SudokuInfo::enter_cell), this method does
259    /// not remove cell options that are invalidated by the new digit. This is
260    /// done for performance reasons to allow batching of multiple changes
261    /// before updating the options. To ensure the cell options are up-to-date,
262    /// call [invalidate](SudokuInfo::invalidate) after making any changes.
263    ///
264    /// # Arguments
265    ///
266    /// * `column`: The column (x-coordinate) of the assigned cell. Must be in
267    /// the range `[0, size[`.
268    /// * `row`: The row (y-coordinate) of the assigned cell. Must be in the
269    /// range `[0, size[`.
270    /// * `number`: The number to assign to the specified cell. Must be in the
271    /// range `[1, size]`.
272    ///
273    /// # Errors
274    ///
275    /// * `SudokuError::OutOfBounds` If either `column` or `row` are not in the
276    /// specified range.
277    /// * `SudokuError::InvalidNumber` If `number` is not in the specified
278    /// range.
279    pub fn enter_cell_no_update(&mut self, column: usize, row: usize,
280            number: usize) -> SudokuResult<()> {
281        self.sudoku.grid_mut().set_cell(column, row, number)?;
282        let options = self.get_options_mut(column, row).unwrap();
283        options.clear();
284        options.insert(number).unwrap();
285        self.up_to_date = false;
286        Ok(())
287    }
288
289    /// Sets the content of the cell at the specified position to the given
290    /// number. If the cell was not empty, the old number will be overwritten.
291    ///
292    /// In contrast with
293    /// [enter_cell_no_update](SudokuInfo::enter_cell_no_update), this method
294    /// immediately removes all cell options that are invalidated by the new
295    /// digit.
296    ///
297    /// # Arguments
298    ///
299    /// * `column`: The column (x-coordinate) of the assigned cell. Must be in
300    /// the range `[0, size[`.
301    /// * `row`: The row (y-coordinate) of the assigned cell. Must be in the
302    /// range `[0, size[`.
303    /// * `number`: The number to assign to the specified cell. Must be in the
304    /// range `[1, size]`.
305    ///
306    /// # Errors
307    ///
308    /// * `SudokuError::OutOfBounds` If either `column` or `row` are not in the
309    /// specified range.
310    /// * `SudokuError::InvalidNumber` If `number` is not in the specified
311    /// range.
312    pub fn enter_cell(&mut self, column: usize, row: usize, number: usize)
313            -> SudokuResult<()> {
314        self.enter_cell_no_update(column, row, number)?;
315        self.update();
316        Ok(())
317    }
318
319    fn update(&mut self) {
320        let size = self.size();
321        let mut options_to_remove = Vec::new();
322        let enqueued_cells: Vec<(usize, usize, usize)> =
323            self.enqueued_cells.drain(..).collect();
324
325        for (column, row, number) in enqueued_cells {
326            self.enter_cell_no_update(column, row, number).unwrap();
327        }
328
329        for row in 0..size {
330            for column in 0..size {
331                let content = self.sudoku.grid().get_cell(column, row)
332                    .unwrap();
333
334                if content.is_some() {
335                    continue;
336                }
337
338                // TODO find a way to use get_options without triggering the
339                // borrow checker
340
341                let options =
342                    &mut self.cell_options[crate::index(column, row, size)];
343                options_to_remove.clear();
344
345                for option in options.iter() {
346                    let is_valid = self.sudoku
347                        .is_valid_number(column, row, option)
348                        .unwrap();
349
350                    if !is_valid {
351                        options_to_remove.push(option);
352                    }
353                }
354
355                for &option_to_remove in options_to_remove.iter() {
356                    options.remove(option_to_remove).unwrap();
357                }
358            }
359        }
360
361        self.up_to_date = true;
362    }
363
364    /// Removes all cell options that have been invalidated by digits entered
365    /// using [enter_cell_no_update](SudokuInfo::enter_cell_no_update) which
366    /// have not yet been processed. If there are no pending digits, nothing
367    /// will be done.
368    pub fn invalidate(&mut self) {
369        if !self.up_to_date {
370            self.update();
371        }
372    }
373
374    /// Gets a [USizeSet] of the possible digits that can be entered into the
375    /// cell at the given position according to this grid info.
376    ///
377    /// Note that, because options are adapted to new digits lazily, this
378    /// operation may require changes to this instance, namely if digits were
379    /// changed since the last call to `get_options` or `get_options_mut`. This
380    /// means a mutable reference to this instance is required.
381    ///
382    /// # Arguments
383    ///
384    /// * `column`: The column (x-coordinate) of the desired cell. Must be in
385    /// the range `[0, size[`.
386    /// * `row`: The row (y-coordinate) of the desired cell. Must be in the
387    /// range `[0, size[`.
388    ///
389    /// # Errors
390    ///
391    /// If either `column` or `row` are not in the specified range. In that
392    /// case, `SudokuError::OutOfBounds` is returned.
393    pub fn get_options(&self, column: usize, row: usize)
394            -> SudokuResult<&USizeSet> {
395        let index = self.verified_index(column, row)?;
396        Ok(&self.cell_options[index])
397    }
398
399    /// Gets a mutable reference to the [USizeSet] that tracks the possible
400    /// digits that can be entered into the cell at the given position
401    /// according to this grid info.
402    ///
403    /// Note that, because options are adapted to new digits lazily, this
404    /// operation may require changes to this instance, namely if digits were
405    /// changed since the last call to `get_options` or `get_options_mut`.
406    ///
407    /// # Arguments
408    ///
409    /// * `column`: The column (x-coordinate) of the desired cell. Must be in
410    /// the range `[0, size[`.
411    /// * `row`: The row (y-coordinate) of the desired cell. Must be in the
412    /// range `[0, size[`.
413    ///
414    /// # Errors
415    ///
416    /// If either `column` or `row` are not in the specified range. In that
417    /// case, `SudokuError::OutOfBounds` is returned.
418    pub fn get_options_mut(&mut self, column: usize, row: usize)
419            -> SudokuResult<&mut USizeSet> {
420        let index = self.verified_index(column, row)?;
421        Ok(&mut self.cell_options[index])
422    }
423
424    /// Gets the total size of the grid for which this instance tracks
425    /// information on one axis (horizontally or vertically). Since grids are
426    /// always squares, this is guaranteed to be valid for both axes.
427    pub fn size(&self) -> usize {
428        self.sudoku.grid().size()
429    }
430
431    /// Gets a read-only reference to the vector storing the options for every
432    /// cell in a [USizeSet]. The cells are in eft-to-right, top-to-bottom
433    /// order, where rows are together.
434    pub fn cell_options(&self) -> &Vec<USizeSet> {
435        &self.cell_options
436    }
437
438    /// Gets a mutable reference to the vector storing the options for every
439    /// cell in a [USizeSet]. The cells are in left-to-right, top-to-bottom
440    /// order, where rows are together.
441    pub fn cell_options_mut(&mut self) -> &mut Vec<USizeSet> {
442        &mut self.cell_options
443    }
444
445    /// Gets the width (number of columns) of one sub-block of the Sudoku. To
446    /// ensure a square grid, this is also the number of blocks that compose
447    /// the grid vertically.
448    ///
449    /// This is syntactic sugar for `x.sudoku().grid().block_width()`.
450    pub fn block_width(&self) -> usize {
451        self.sudoku.grid().block_width()
452    }
453
454    /// Gets the height (number of rows) of one sub-block of the Sudoku. To
455    /// ensure a square grid, this is also the number of blocks that compose
456    /// the grid horizontally.
457    ///
458    /// This is syntactic sugar for `x.sudoku().grid().block_height()`.
459    pub fn block_height(&self) -> usize {
460        self.sudoku.grid().block_height()
461    }
462
463    /// Assigns the content of another grid info to this one, that is, after
464    /// the operation this grid info will equal `other`. The dimensions must be
465    /// equivalent.
466    ///
467    /// # Errors
468    ///
469    /// If `other` has different dimensions to this instance. In that case,
470    /// `SudokuError::InvalidDimensions` is returned.
471    pub fn assign(&mut self, other: &SudokuInfo<C>) -> SudokuResult<()> {
472        self.sudoku.grid_mut().assign(other.sudoku.grid())?;
473
474        for i in 0..self.cell_options.len() {
475            self.cell_options[i] = other.cell_options[i].clone();
476        }
477
478        Ok(())
479    }
480
481    /// Gets the [Sudoku] for which this Sudoku info stores additional
482    /// information.
483    pub fn sudoku(&self) -> &Sudoku<C> {
484        &self.sudoku
485    }
486
487    /// Gets a mutable reference to the [Sudoku] for which this Sudoku info
488    /// stores additional information.
489    pub fn sudoku_mut(&mut self) -> &mut Sudoku<C> {
490        &mut self.sudoku
491    }
492
493    fn op(&mut self, other: &SudokuInfo<C>,
494            single_op: impl Fn((&mut Option<usize>, &mut USizeSet),
495                (&Option<usize>, &USizeSet)) -> bool)
496            -> SudokuResult<bool> {
497        if self.block_width() != other.block_width() ||
498                self.block_height() != other.block_height() {
499            return Err(SudokuError::InvalidDimensions);
500        }
501
502        let mut changed = false;
503        let iter = (&mut self.sudoku).grid_mut().cells_mut().iter_mut()
504            .zip((&mut self.cell_options).iter_mut())
505            .zip(other.sudoku().grid().cells().iter()
506                .zip(other.cell_options().iter()));
507        
508        for (self_info, other_info) in iter {
509            changed |= single_op(self_info, other_info);
510        }
511
512        if changed {
513            self.update();
514        }
515
516        Ok(changed)
517    }
518
519    /// Intersects this Sudoku info with the given other one, implying that all
520    /// information of both is correct. All cells that are filled in in either
521    /// will be written in the result and only options that are present in both
522    /// will be retained. Note that contradictions (different digits in this
523    /// and the other Sudoku info) will result in the cell being cleared and
524    /// all options being removed.
525    pub fn intersect_assign(&mut self, other: &SudokuInfo<C>)
526            -> SudokuResult<bool> {
527        self.op(other, |(self_cell, self_options), (other_cell, other_options)| {
528            let cells_changed = if let Some(number) = other_cell {
529                let old_number = self_cell.replace(*number);
530
531                if Some(*number) == old_number {
532                    false
533                }
534                else {
535                    if old_number.is_some() {
536                        self_options.clear();
537                        self_cell.take();
538                    }
539
540                    true
541                }
542            }
543            else {
544                false
545            };
546            let options_changed =
547                self_options.intersect_assign(other_options).unwrap();
548            cells_changed || options_changed
549        })
550    }
551
552    /// Unifies this Sudoku info with the given other one, implying that all
553    /// information of both *could* be correct. Options present in at least one
554    /// will be put in the result and digits are only retained if they are
555    /// present in both (unless the other does not have any options for that
556    /// cell). Note that contradictions (different digits in this
557    /// and the other Sudoku info) will result in the cell being cleared and
558    /// both numbers being put in the option set.
559    pub fn union_assign(&mut self, other: &SudokuInfo<C>)
560            -> SudokuResult<bool> {
561        self.op(other, |(self_cell, self_options), (other_cell, other_options)| {
562            let content_changed = if let Some(self_number) = self_cell {
563                if &Some(*self_number) == other_cell || other_options.is_empty() {
564                    false
565                }
566                else {
567                    self_cell.take();
568                    true
569                }
570            }
571            else if self_options.is_empty() {
572                *self_cell = *other_cell;
573                self_cell.is_some()
574            }
575            else {
576                false
577            };
578            let options_changed = self_options.union_assign(other_options).unwrap();
579            content_changed || options_changed
580        })
581    }
582}
583
584impl<C: Constraint + Clone> PartialEq for SudokuInfo<C> {
585    fn eq(&self, other: &Self) -> bool {
586        if self.sudoku().grid() != other.sudoku().grid() {
587            false
588        }
589        else if !self.up_to_date {
590            let mut lhs = self.clone();
591            lhs.update();
592            lhs.eq(other)
593        }
594        else if !other.up_to_date {
595            let mut other = other.clone();
596            other.update();
597            self.eq(&other)
598        }
599        else {
600            self.cell_options() == other.cell_options()
601        }
602    }
603}
604
605/// A trait for strategies, which use logical reasoning to restrict the
606/// possibilities of a Sudoku.
607pub trait Strategy {
608
609    /// Applies this strategy to the given Sudoku. The strategy may rely on and
610    /// modify the information in the given `sudoku_info`. This instance is
611    /// given to other strategies that participate in the solution and/or
612    /// future iterations of the same strategy. It can thus be used to
613    /// communicate insights.
614    ///
615    /// This method shall return `true` if and only if something has changed,
616    /// that is, a digit has been entered or an option has been removed.
617    fn apply(&self, sudoku_info: &mut SudokuInfo<impl Constraint + Clone>)
618        -> bool;
619}
620
621#[cfg(test)]
622mod tests {
623
624    use super::*;
625
626    use crate::Sudoku;
627    use crate::constraint::DefaultConstraint;
628
629    #[test]
630    fn sudoku_info_equality() {
631        let sudoku = Sudoku::parse("2x2;\
632             ,1, ,4,\
633             ,2,3, ,\
634             , , ,2,\
635             , , , ", DefaultConstraint).unwrap();
636        let mut si1 = SudokuInfo::from_sudoku(sudoku.clone());
637        let mut si2 = SudokuInfo::from_sudoku(sudoku);
638
639        assert!(si1 == si2);
640
641        si1.enter_cell_no_update(3, 1, 1).unwrap();
642        assert!(si1 != si2);
643        si2.enter_cell_no_update(3, 1, 1).unwrap();
644        assert!(si1 == si2);
645
646        si1.update();
647        assert!(si1 == si2);
648        si2.update();
649        assert!(si1 == si2);
650
651        si1.get_options_mut(0, 3).unwrap().remove(1).unwrap();
652        assert!(si1 != si2);
653    }
654    
655    fn get_different_sudoku_infos()
656            -> (SudokuInfo<DefaultConstraint>, SudokuInfo<DefaultConstraint>) {
657        let sudoku = Sudoku::parse("2x2;\
658             ,1, ,4,\
659             ,2,3, ,\
660             , , ,2,\
661             , , , ", DefaultConstraint).unwrap();
662        let mut si1 = SudokuInfo::from_sudoku(sudoku.clone());
663        let mut si2 = SudokuInfo::from_sudoku(sudoku);
664        
665        si1.enter_cell(2, 0, 2).unwrap();
666        si1.enter_cell(3, 1, 1).unwrap();
667        si2.enter_cell(3, 1, 1).unwrap();
668        
669        si1.get_options_mut(0, 3).unwrap().remove(1).unwrap();
670        si2.get_options_mut(0, 3).unwrap().remove(1).unwrap();
671        si2.get_options_mut(1, 3).unwrap().remove(3).unwrap();
672        
673        (si1, si2)
674    }
675
676    #[test]
677    fn sudoku_info_union() {
678        let (mut si1, si2) = get_different_sudoku_infos();
679
680        assert!(si1.union_assign(&si2).unwrap());
681
682        assert_eq!(None, si1.get_cell(2, 0).unwrap());
683        assert_eq!(Some(1), si1.get_cell(3, 1).unwrap());
684
685        assert!(!si1.get_options(0, 3).unwrap().contains(1));
686        assert!(si1.get_options(1, 3).unwrap().contains(3));
687    }
688
689    #[test]
690    fn sudoku_info_intersect() {
691        let (mut si1, si2) = get_different_sudoku_infos();
692
693        assert!(si1.intersect_assign(&si2).unwrap());
694
695        assert_eq!(Some(2), si1.get_cell(2, 0).unwrap());
696        assert_eq!(Some(1), si1.get_cell(3, 1).unwrap());
697
698        assert!(!si1.get_options(0, 3).unwrap().contains(1));
699        assert!(!si1.get_options(1, 3).unwrap().contains(3));
700    }
701
702    #[test]
703    fn sudoku_info_operation_err() {
704        let sudoku1 = Sudoku::parse("1x2;1,2,2,1", DefaultConstraint).unwrap();
705        let sudoku2 = Sudoku::parse("2x1;1,2,2,1", DefaultConstraint).unwrap();
706        let mut si1 = SudokuInfo::from_sudoku(sudoku1);
707        let si2 = SudokuInfo::from_sudoku(sudoku2);
708
709        assert_eq!(Err(SudokuError::InvalidDimensions), si1.union_assign(&si2));
710        assert_eq!(Err(SudokuError::InvalidDimensions), si1.intersect_assign(&si2));
711    }
712}