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}