lisudoku_solver/
solver.rs

1use crate::solver::logical_solver::arrow_advanced_candidates::ArrowAdvancedCandidates;
2use crate::solver::logical_solver::common_peer_elimination_arrow::CommonPeerEliminationArrow;
3use crate::solver::logical_solver::kropki_advanced_candidates::KropkiAdvancedCandidates;
4use crate::solver::logical_solver::nishio_forcing_chains::NishioForcingChains;
5use crate::solver::logical_solver::renban_candidates::RenbanCandidates;
6use crate::types::{Area, Arrow, CellDirection, CellPosition, Grid, KillerCage, KropkiDot, KropkiDotType, Rule, SudokuConstraints, SudokuGrid};
7use std::cell::RefCell;
8use std::collections::{HashSet, HashMap};
9use std::cmp::{min, max};
10use std::ops::BitAnd;
11use std::rc::Rc;
12use itertools::Itertools;
13use logical_solver::adhoc_naked_set::AdhocNakedSet;
14use logical_solver::palindrome_candidates::PalindromeCandidates;
15use logical_solver::palindrome_values::PalindromeValues;
16use self::logical_solver::advanced_candidates::CellEliminationsResult;
17use self::logical_solver::arrow_candidates::ArrowCombinationLogicFactory;
18use self::logical_solver::candidates::Candidates;
19use self::logical_solver::combinations::cell_combination_logic::CellsCacheKey;
20use self::logical_solver::common_peer_elimination::CommonPeerElimination;
21use self::logical_solver::common_peer_elimination_kropki::CommonPeerEliminationKropki;
22use self::logical_solver::hidden_set::HiddenSet;
23use self::logical_solver::hidden_singles::HiddenSingles;
24use self::logical_solver::killer45::Killer45;
25use self::logical_solver::killer_candidates::KillerCandidates;
26use self::logical_solver::kropki_chain_candidates::KropkiChainCandidates;
27use self::logical_solver::locked_candidates::LockedCandidates;
28use self::logical_solver::naked_set::NakedSet;
29use self::logical_solver::naked_singles::NakedSingle;
30use self::logical_solver::technique::Technique;
31use self::logical_solver::thermo_candidates::ThermoCandidates;
32use self::logical_solver::thermo_steps::Thermo;
33use self::logical_solver::top_bottom_candidates::TopBottomCandidates;
34use self::logical_solver::x_wing::XWing;
35use self::logical_solver::xy_wing::XYWing;
36use self::logical_solver::turbot_fish::TurbotFish;
37use self::logical_solver::empty_reclanges::EmptyRectangles;
38use crate::solver::logical_solver::arrow_candidates::ArrowCandidates;
39
40mod checker;
41pub mod logical_solver;
42mod brute_solver;
43
44const KNIGHT_MOVES: [CellDirection; 8] = [
45  CellDirection { row: 1, col: 2 },
46  CellDirection { row: 1, col: -2 },
47  CellDirection { row: -1, col: 2 },
48  CellDirection { row: -1, col: -2 },
49  CellDirection { row: 2, col: 1 },
50  CellDirection { row: 2, col: -1 },
51  CellDirection { row: -2, col: 1 },
52  CellDirection { row: -2, col: -1 },
53];
54
55const KING_MOVES: [CellDirection; 8] = [
56  CellDirection { row: -1, col: -1 },
57  CellDirection { row: -1, col: 0 },
58  CellDirection { row: -1, col: 1 },
59  CellDirection { row: 0, col: -1 },
60  CellDirection { row: 0, col: 1 },
61  CellDirection { row: 1, col: -1 },
62  CellDirection { row: 1, col: 0 },
63  CellDirection { row: 1, col: 1 },
64];
65
66const ADJACENT_MOVES: [CellDirection; 4] = [
67  CellDirection { row: 0, col: 1 },
68  CellDirection { row: 0, col: -1 },
69  CellDirection { row: 1, col: 0 },
70  CellDirection { row: -1, col: 0 },
71];
72
73pub struct Solver {
74  pub constraints: SudokuConstraints,
75  pub techniques: Vec<Rc<dyn Technique>>,
76  pub grid: Grid,
77  pub solution: Option<Grid>,
78  grid_to_regions: Vec<Vec<Vec<usize>>>,
79  grid_to_thermos: Vec<Vec<Vec<usize>>>,
80  grid_to_killer_cage: Vec<Vec<usize>>,
81  grid_to_kropki_dots: Vec<Vec<Vec<usize>>>,
82  grid_to_odd_cells: Vec<Vec<bool>>,
83  grid_to_even_cells: Vec<Vec<bool>>,
84  grid_to_renbans: Vec<Vec<Vec<usize>>>,
85  candidates_active: bool,
86  candidates: Vec<Vec<HashSet<u32>>>,
87  hint_mode: bool,
88  step_count_limit: Option<usize>,
89  arrow_combinatons_logic_factory: RefCell<ArrowCombinationLogicFactory>,
90  cell_eliminations_cache: RefCell<HashMap<CellsCacheKey, CellEliminationsResult>>,
91}
92
93impl Clone for Solver {
94  fn clone(&self) -> Self {
95    Self {
96      constraints: self.constraints.clone(),
97      techniques: self.techniques.clone(),
98      grid: self.grid.clone(),
99      solution: self.solution.clone(),
100      grid_to_regions: self.grid_to_regions.clone(),
101      grid_to_thermos: self.grid_to_thermos.clone(),
102      grid_to_killer_cage: self.grid_to_killer_cage.clone(),
103      grid_to_kropki_dots: self.grid_to_kropki_dots.clone(),
104      grid_to_odd_cells: self.grid_to_odd_cells.clone(),
105      grid_to_even_cells: self.grid_to_even_cells.clone(),
106      grid_to_renbans: self.grid_to_renbans.clone(),
107      candidates_active: self.candidates_active.clone(),
108      candidates: self.candidates.clone(),
109      hint_mode: self.hint_mode.clone(),
110      step_count_limit: self.step_count_limit.clone(),
111      arrow_combinatons_logic_factory: RefCell::new(ArrowCombinationLogicFactory::new()),
112      cell_eliminations_cache: self.cell_eliminations_cache.clone(),
113    }
114  }
115}
116
117impl Solver {
118  pub fn new(mut constraints: SudokuConstraints, input_grid: Option<SudokuGrid>) -> Solver {
119    // Assume all extra regions contain grid_size cells
120    constraints.regions.extend(constraints.extra_regions.to_vec());
121
122    let mut grid_to_regions = vec![ vec![ vec![]; constraints.grid_size ]; constraints.grid_size ];
123    for (index, region) in constraints.regions.iter().enumerate() {
124      for cell in region {
125        grid_to_regions[cell.row][cell.col].push(index);
126      }
127    }
128
129    let mut grid_to_thermos = vec![ vec![ vec![]; constraints.grid_size ]; constraints.grid_size ];
130    for (index, thermo) in constraints.thermos.iter().enumerate() {
131      for cell in thermo {
132        grid_to_thermos[cell.row][cell.col].push(index);
133      }
134    }
135
136    let mut grid_to_killer_cage = vec![ vec![ usize::MAX; constraints.grid_size ]; constraints.grid_size ];
137    for (index, killer_cage) in constraints.killer_cages.iter().enumerate() {
138      for cell in &killer_cage.region {
139        grid_to_killer_cage[cell.row][cell.col] = index;
140      }
141    }
142
143    let mut grid_to_kropki_dots = vec![ vec![ vec![]; constraints.grid_size ]; constraints.grid_size ];
144    for (index, kropki_dot) in constraints.kropki_dots.iter().enumerate() {
145      let KropkiDot { dot_type: _, cell_1, cell_2 } = kropki_dot;
146      grid_to_kropki_dots[cell_1.row][cell_1.col].push(index);
147      grid_to_kropki_dots[cell_2.row][cell_2.col].push(index);
148    }
149    if constraints.kropki_negative {
150      for row in 0..constraints.grid_size {
151        for col in 0..constraints.grid_size {
152          let cell = CellPosition::new(row, col);
153          let adjacent_cells: HashSet<CellPosition> = Self::get_adjacent_cells(cell, constraints.grid_size).into_iter().collect();
154          let dot_cells: HashSet<CellPosition> = grid_to_kropki_dots[row][col].iter()
155            .map(|&kropki_dot_index| {
156              let kropki_dot = &constraints.kropki_dots[kropki_dot_index];
157              let other_cell = kropki_dot.other_cell(&cell);
158              other_cell
159            })
160            .collect();
161          let negative_cells: HashSet<CellPosition> = adjacent_cells.difference(&dot_cells).copied().collect();
162          for negative_cell in negative_cells.into_iter().sorted() {
163            let kropki_dot_index = constraints.kropki_dots.len();
164            grid_to_kropki_dots[cell.row][cell.col].push(kropki_dot_index);
165            grid_to_kropki_dots[negative_cell.row][negative_cell.col].push(kropki_dot_index);
166            constraints.kropki_dots.push(KropkiDot {
167              dot_type: KropkiDotType::Negative,
168              cell_1: cell,
169              cell_2: negative_cell,
170            })
171          }
172        }
173      }
174    }
175
176    let mut grid_to_odd_cells = vec![ vec![ false; constraints.grid_size ]; constraints.grid_size ];
177    for cell in &constraints.odd_cells {
178      grid_to_odd_cells[cell.row][cell.col] = true;
179    }
180
181    let mut grid_to_even_cells = vec![ vec![ false; constraints.grid_size ]; constraints.grid_size ];
182    for cell in &constraints.even_cells {
183      grid_to_even_cells[cell.row][cell.col] = true;
184    }
185
186    let mut grid_to_renbans = vec![ vec![ vec![]; constraints.grid_size ]; constraints.grid_size ];
187    for (index, renban) in constraints.renbans.iter().enumerate() {
188      for cell in renban {
189        grid_to_renbans[cell.row][cell.col].push(index);
190      }
191    }
192
193    let grid = if input_grid.is_some() {
194      input_grid.unwrap().values
195    } else {
196      let mut initial_grid = vec![ vec![ 0; constraints.grid_size ]; constraints.grid_size ];
197      for fixed_number in &constraints.fixed_numbers {
198        initial_grid[fixed_number.position.row][fixed_number.position.col] = fixed_number.value;
199      }
200      initial_grid
201    };
202
203    let candidates = vec![ vec![ HashSet::new(); constraints.grid_size ]; constraints.grid_size ];
204
205    Solver {
206      constraints,
207      grid,
208      solution: None,
209      grid_to_regions,
210      grid_to_thermos,
211      grid_to_killer_cage,
212      grid_to_kropki_dots,
213      grid_to_odd_cells,
214      grid_to_even_cells,
215      grid_to_renbans,
216      candidates_active: false,
217      candidates,
218      hint_mode: false,
219      step_count_limit: None,
220      techniques: Self::default_techniques(),
221      arrow_combinatons_logic_factory: RefCell::new(ArrowCombinationLogicFactory::new()),
222      cell_eliminations_cache: RefCell::new(HashMap::new()),
223    }
224  }
225
226  pub fn with_hint_mode(mut self, flag: bool) -> Self {
227    self.hint_mode = flag;
228    self
229  }
230
231  pub fn with_step_count_limit(mut self, step_count_limit: usize) -> Self {
232    self.step_count_limit = Some(step_count_limit);
233    self
234  }
235
236  pub fn default_techniques() -> Vec<Rc<dyn Technique>> {
237    vec![
238      Rc::new(ThermoCandidates),
239      Rc::new(KillerCandidates),
240      Rc::new(KropkiChainCandidates::new(false)),
241      Rc::new(KropkiChainCandidates::new(true)),
242      Rc::new(TopBottomCandidates::new(false)),
243      Rc::new(ArrowCandidates),
244      Rc::new(RenbanCandidates),
245      Rc::new(PalindromeValues),
246      Rc::new(PalindromeCandidates),
247      Rc::new(NakedSingle),
248      Rc::new(HiddenSingles),
249      Rc::new(Thermo),
250      Rc::new(Candidates),
251      Rc::new(Killer45),
252      Rc::new(LockedCandidates::new(2)),
253      Rc::new(NakedSet::new(2)),
254      Rc::new(HiddenSet::new(2)),
255      Rc::new(LockedCandidates::new(3)),
256      Rc::new(NakedSet::new(3)),
257      Rc::new(HiddenSet::new(3)),
258      Rc::new(XWing),
259      Rc::new(XYWing),
260      Rc::new(CommonPeerElimination),
261      Rc::new(CommonPeerEliminationKropki),
262      Rc::new(KropkiAdvancedCandidates),
263      Rc::new(ArrowAdvancedCandidates),
264      Rc::new(CommonPeerEliminationArrow),
265      Rc::new(AdhocNakedSet),
266      Rc::new(TurbotFish),
267      Rc::new(EmptyRectangles),
268      // Rc::new(PhistomefelRing), // disabled for now...
269      Rc::new(NishioForcingChains),
270    ]
271  }
272
273  pub fn with_techniques(mut self, techniques: Vec<Rc<dyn Technique>>) -> Self {
274    self.techniques = techniques;
275    self
276  }
277
278  pub fn without_techniques(mut self, techniques: Vec<Rc<dyn Technique>>) -> Self {
279    let rules: Vec<Rule> = techniques.into_iter().map(|t| t.get_rule()).collect();
280    self.techniques = self.techniques.into_iter().filter(|t| !rules.contains(&t.get_rule())).collect();
281    self
282  }
283 
284  fn get_adjacent_cells(cell: CellPosition, grid_size: usize) -> Vec<CellPosition> {
285    ADJACENT_MOVES.iter().filter_map(|direction| {
286      let prow = cell.row as isize + direction.row;
287      let pcol = cell.col as isize + direction.col;
288      if prow < 0 || prow >= grid_size as isize ||
289         pcol < 0 || pcol >= grid_size as isize {
290        return None
291      }
292      let peer = CellPosition {
293        row: prow as usize,
294        col: pcol as usize,
295      };
296      Some(peer)
297    }).collect()
298  }
299
300  fn compute_area_cell_candidates(&self, area: &Area, cell: &CellPosition) -> HashSet<u32> {
301    match area {
302      #[allow(unused_parens)]
303      (
304        &Area::Adhoc(_) | &Area::Row(_) | &Area::Column(_) | &Area::Region(_) | &Area::Renban(_) |
305        &Area::PrimaryDiagonal | &Area::SecondaryDiagonal
306      ) => self.compute_generic_area_cell_candidates(area),
307      &Area::Thermo(thermo_index) => self.compute_thermo_cell_candidates(thermo_index, cell),
308      &Area::KillerCage(killer_cage_index) => self.compute_killer_cell_candidates(killer_cage_index),
309      &Area::KropkiDot(_) => {
310        // Do not enforce candidates directly, use an explicit rule for that
311        self.compute_all_candidates()
312      },
313      &Area::Grid | &Area::Cell(_, _) | &Area::Arrow(_) | &Area::Palindrome(_) => unimplemented!(),
314    }
315  }
316
317  fn compute_generic_area_cell_candidates(&self, area: &Area) -> HashSet<u32> {
318    let mut set = self.compute_all_candidates();
319    for CellPosition { row, col } in self.get_area_cells(area) {
320      if self.grid[row][col] != 0 {
321        set.remove(&self.grid[row][col]);
322      }
323    }
324    set
325  }
326
327  fn compute_killer_cell_candidates(&self, killer_cage_index: usize) -> HashSet<u32> {
328    let area = Area::KillerCage(killer_cage_index);
329    let mut set = self.compute_generic_area_cell_candidates(&area);
330
331    let killer_cage = &self.constraints.killer_cages[killer_cage_index];
332    if let Some(sum) = killer_cage.sum {
333      for value in (sum+1)..=(self.constraints.grid_size as u32) {
334        set.remove(&value);
335      }
336    }
337
338    set
339  }
340
341  // This could be made more intelligent, but we leave the tricks to logical_solver
342  fn compute_thermo_cell_candidates(&self, thermo_index: usize, area_cell: &CellPosition) -> HashSet<u32> {
343    let thermo = &self.constraints.thermos[thermo_index];
344
345    let mut after = false;
346    let mut max_before = 0;
347    let mut min_after = self.constraints.grid_size as u32 + 1;
348
349    for cell in thermo {
350      if area_cell.row == cell.row && area_cell.col == cell.col {
351        after = true;
352        continue
353      }
354      let value = self.grid[cell.row][cell.col];
355      if value == 0 {
356        continue
357      }
358
359      if after {
360        min_after = min(min_after, value);
361      } else {
362        max_before = max(max_before, value);
363      }
364    }
365
366    let set: HashSet<u32> = (max_before+1..=min_after-1).collect();
367
368    set
369  }
370
371  fn compute_cell_candidates(&self, cell: &CellPosition) -> HashSet<u32> {
372    if self.grid[cell.row][cell.col] != 0 {
373      return HashSet::new()
374    }
375
376    if self.candidates_active {
377      return self.candidates[cell.row][cell.col].clone();
378    }
379
380    self.recompute_cell_candidates(cell)
381  }
382
383  // Note: update when adding constraints
384  // We don't apply all restrictions at this level (e.g. thermo, palindrome)
385  fn recompute_cell_candidates(&self, cell: &CellPosition) -> HashSet<u32> {
386    let mut candidates = self.compute_all_candidates();
387    for peer in self.get_cell_peers(cell, false) {
388      let value = self.grid[peer.row][peer.col];
389        if value == 0 {
390          continue
391        }
392        candidates.remove(&value);
393    }
394
395    for area in &self.get_cell_areas(cell, false) {
396      let area_set = self.compute_area_cell_candidates(area, cell);
397      candidates = candidates.intersection(&area_set).cloned().collect();
398    }
399
400    if self.grid_to_odd_cells[cell.row][cell.col] {
401      candidates = candidates.into_iter().filter(|value| value % 2 == 1).collect();
402    }
403    if self.grid_to_even_cells[cell.row][cell.col] {
404      candidates = candidates.into_iter().filter(|value| value % 2 == 0).collect();
405    }
406
407    candidates
408  }
409
410  fn compute_all_candidates(&self) -> HashSet<u32> {
411    (1..=self.constraints.grid_size as u32).collect::<HashSet<u32>>()
412  }
413
414  // Note: update when adding new areas
415  // Note: we're mostly interested in areas with uniqueness constraints
416  fn get_cell_areas(&self, cell: &CellPosition, include_thermo: bool) -> Vec<Area> {
417    let mut areas: Vec<Area> = vec![];
418
419    areas.extend(self.get_cell_classic_areas(cell));
420    areas.extend(self.get_cell_special_areas(cell, include_thermo));
421
422    areas
423  }
424
425  fn get_cell_classic_areas(&self, cell: &CellPosition) -> Vec<Area> {
426    let &CellPosition { row, col } = cell;
427    let mut areas = vec![ Area::Row(row), Area::Column(col) ];
428
429    for &region_index in &self.grid_to_regions[row][col] {
430      if region_index < self.constraints.grid_size {
431        areas.push(Area::Region(region_index));
432      }
433    }
434
435    areas
436  }
437
438  fn get_cell_special_areas(&self, cell: &CellPosition, include_thermo: bool) -> Vec<Area> {
439    let &CellPosition { row, col } = cell;
440    let mut areas: Vec<Area> = vec![];
441
442    for &region_index in &self.grid_to_regions[row][col] {
443      if region_index >= self.constraints.grid_size {
444        areas.push(Area::Region(region_index));
445      }
446    }
447
448    if self.constraints.primary_diagonal && row == col {
449      areas.push(Area::PrimaryDiagonal);
450    }
451    if self.constraints.secondary_diagonal && row == self.constraints.grid_size - 1 - col {
452      areas.push(Area::SecondaryDiagonal);
453    }
454    let killer_cage_index = self.grid_to_killer_cage[row][col];
455    if killer_cage_index != usize::MAX {
456      areas.push(Area::KillerCage(killer_cage_index));
457    }
458    if include_thermo {
459      for &thermo_index in &self.grid_to_thermos[row][col] {
460        areas.push(Area::Thermo(thermo_index));
461      }
462    }
463    for &renban_index in &self.grid_to_renbans[row][col] {
464      areas.push(Area::Renban(renban_index));
465    }
466
467    areas
468  }
469
470  // Note: update when adding new areas
471  // Note: a lot of the time we don't want area that don't need all <grid_size> and unique values
472  fn get_all_areas(
473    &self, include_thermo: bool, include_killer: bool, include_kropki: bool, include_renban: bool,
474    include_palindrome: bool,
475  ) -> Vec<Area> {
476    let mut areas = vec![];
477    areas.extend(self.get_row_areas());
478    areas.extend(self.get_col_areas());
479    areas.extend(self.get_region_areas());
480    if self.constraints.primary_diagonal {
481      areas.push(Area::PrimaryDiagonal);
482    }
483    if self.constraints.secondary_diagonal {
484      areas.push(Area::SecondaryDiagonal);
485    }
486    if include_thermo {
487      for thermo_index in 0..self.constraints.thermos.len() {
488        areas.push(Area::Thermo(thermo_index));
489      }
490    }
491    if include_killer {
492      for killer_cage_index in 0..self.constraints.killer_cages.len() {
493        areas.push(Area::KillerCage(killer_cage_index));
494      }
495    }
496    if include_kropki {
497      for kropki_dot_index in 0..self.constraints.kropki_dots.len() {
498        areas.push(Area::KropkiDot(kropki_dot_index));
499      }
500    }
501    if include_renban {
502      for renban_index in 0..self.constraints.renbans.len() {
503        areas.push(Area::Renban(renban_index));
504      }
505    }
506    if include_palindrome {
507      for palindrome_index in 0..self.constraints.palindromes.len() {
508        areas.push(Area::Palindrome(palindrome_index));
509      }
510    }
511
512    areas
513  }
514
515  fn get_all_proper_areas(&self) -> Vec<Area> {
516    self.get_all_areas(false, false, false, false, false)
517  }
518
519  fn get_row_areas(&self) -> Vec<Area> {
520    (0..self.constraints.grid_size).map(|row| Area::Row(row)).collect()
521  }
522
523  fn get_col_areas(&self) -> Vec<Area> {
524    (0..self.constraints.grid_size).map(|col| Area::Column(col)).collect()
525  }
526
527  fn get_region_areas(&self) -> Vec<Area> {
528    (0..self.constraints.regions.len()).map(|region_index| {
529      Area::Region(region_index)
530    }).collect()
531  }
532
533  fn get_area_cells(&self, area: &Area) -> Vec<CellPosition> {
534    match area {
535      &Area::Grid => self.get_grid_cells(),
536      Area::Adhoc(cells) => cells.to_vec(),
537      &Area::Cell(row, col) => vec![CellPosition::new(row, col)],
538      &Area::Row(row) => self.get_row_cells(row),
539      &Area::Column(col) => self.get_col_cells(col),
540      &Area::Region(region_index) => self.constraints.regions[region_index].to_vec(),
541      &Area::Thermo(thermo_index) => self.constraints.thermos[thermo_index].to_vec(),
542      &Area::KillerCage(killer_cage_index) => {
543        self.constraints.killer_cages[killer_cage_index].region.to_vec()
544      },
545      &Area::KropkiDot(kropki_dot_index) => {
546        let kropki_dot = &self.constraints.kropki_dots[kropki_dot_index];
547        vec![ kropki_dot.cell_1, kropki_dot.cell_2 ]
548      },
549      &Area::PrimaryDiagonal => self.get_primary_diagonal_cells(),
550      &Area::SecondaryDiagonal => self.get_secondary_diagonal_cells(),
551      &Area::Renban(renban_index) => self.constraints.renbans[renban_index].to_vec(),
552      &Area::Palindrome(palindrome_index) => self.constraints.palindromes[palindrome_index].to_vec(),
553      &Area::Arrow(_) => unimplemented!(),
554    }
555  }
556
557  fn get_area_values(&self, area: &Area) -> Vec<u32> {
558    self.get_area_cells(&area).into_iter().map(|cell| {
559      let value = self.grid[cell.row][cell.col];
560      value
561    }).collect()
562  }
563
564  fn get_empty_area_cells(&self, area: &Area) -> Vec<CellPosition> {
565    self.get_area_cells(area).into_iter().filter(|cell| self.grid[cell.row][cell.col] == 0).collect()
566  }
567
568  fn get_all_empty_cells(&self) -> Vec<CellPosition> {
569    self.get_empty_area_cells(&Area::Grid)
570  }
571
572  #[allow(dead_code)]
573  fn get_all_cells_with_candidate(&self, value: u32) -> Vec<CellPosition> {
574    self.get_all_empty_cells()
575        .into_iter()
576        .filter(|&CellPosition { row, col }| self.candidates[row][col].contains(&value))
577        .collect()
578  }
579
580  fn get_grid_cells(&self) -> Vec<CellPosition> {
581    (0..self.constraints.grid_size).flat_map(|row| {
582      (0..self.constraints.grid_size).map(|col| {
583        CellPosition::new(row, col)
584      }).collect::<Vec<CellPosition>>()
585    }).collect()
586  }
587
588  fn get_row_cells(&self, row: usize) -> Vec<CellPosition> {
589    (0..self.constraints.grid_size).map(|col| CellPosition::new(row, col)).collect()
590  }
591
592  fn get_col_cells(&self, col: usize) -> Vec<CellPosition> {
593    (0..self.constraints.grid_size).map(|row| CellPosition::new(row, col)).collect()
594  }
595
596  fn get_primary_diagonal_cells(&self) -> Vec<CellPosition> {
597    (0..self.constraints.grid_size).map(|index| CellPosition::new(index, index)).collect()
598  }
599
600  fn get_secondary_diagonal_cells(&self) -> Vec<CellPosition> {
601    (0..self.constraints.grid_size).map(|index| {
602      CellPosition::new(index, self.constraints.grid_size - 1 - index)
603    }).collect()
604  }
605
606  #[allow(dead_code)]
607  fn compute_area_candidates_union(&self, area: &Area) -> HashSet<u32> {
608    let mut area_candidates: HashSet<u32> = HashSet::new();
609    for cell in self.get_area_cells(area) {
610      let cell_candidates = self.compute_cell_candidates(&cell);
611      area_candidates = area_candidates.union(&cell_candidates).cloned().collect();
612    }
613    area_candidates
614  }
615
616  fn get_area_cells_with_candidate(&self, area: &Area, value: u32) -> Vec<CellPosition> {
617    self.get_area_cells_with_candidates(area, &HashSet::from([ value ]))
618  }
619
620  fn filter_cells_with_any_candidates(&self, cells: &Vec<CellPosition>, values: &HashSet<u32>) -> Vec<CellPosition> {
621    cells.iter()
622        .filter(|&cell| !self.compute_cell_candidates(cell).is_disjoint(values))
623        .copied()
624        .collect()
625  }
626
627  fn filter_cells_with_subset_candidates(&self, cells: &Vec<CellPosition>, values: &HashSet<u32>) -> Vec<CellPosition> {
628    cells.iter()
629        .filter(|&cell| self.compute_cell_candidates(cell).is_subset(values))
630        .copied()
631        .collect()
632  }
633
634  fn get_area_cells_with_candidates(&self, area: &Area, values: &HashSet<u32>) -> Vec<CellPosition> {
635    let area_cells = self.get_empty_area_cells(area);
636    self.filter_cells_with_any_candidates(&area_cells, values)
637  }
638
639  fn compute_cells_by_value_in_area(&self, area: &Area, candidates: &Vec<Vec<HashSet<u32>>>) -> HashMap<u32, Vec<CellPosition>> {
640    let mut value_cells: HashMap<u32, Vec<CellPosition>> = HashMap::new();
641    for cell in self.get_empty_area_cells(area) {
642      for value in candidates[cell.row][cell.col].iter().sorted() {
643        let entry = value_cells.entry(*value).or_insert(vec![]);
644        entry.push(cell);
645      }
646    }
647    value_cells
648  }
649
650  fn get_cell_peers_with_candidate(&self, cell: &CellPosition, value: u32) -> Vec<CellPosition> {
651    let candidates: HashSet<u32> = HashSet::from([ value ]);
652    self.get_cell_peers_with_candidates(cell, &candidates)
653  }
654
655  fn get_cell_peers_with_candidates(&self, cell: &CellPosition, values: &HashSet<u32>) -> Vec<CellPosition> {
656    let peers = self.get_cell_peers(cell, true);
657    self.filter_cells_with_any_candidates(&peers, values)
658  }
659
660  // Note: update when adding constraints
661  // What it considers as a "peer" is subjective, it doesn't consider
662  // all restrictions at this level (e.g. thermo, palindrome)
663  fn get_cell_peers(&self, cell: &CellPosition, include_thermo: bool) -> Vec<CellPosition> {
664    let mut peers: Vec<CellPosition> = vec![];
665
666    peers.extend(self.get_cell_classic_peers(cell));
667    peers.extend(self.get_cell_special_peers(cell, include_thermo));
668
669    peers.into_iter()
670         .filter(|other_cell| other_cell != cell)
671         .unique()
672         .collect()
673  }
674
675  fn get_cell_classic_peers(&self, cell: &CellPosition) -> Vec<CellPosition> {
676    self.get_cell_classic_areas(cell)
677      .iter()
678      .flat_map(|area| self.get_area_cells(area))
679      .collect()
680  }
681
682  fn get_cell_special_peers(&self, cell: &CellPosition, include_thermo: bool) -> Vec<CellPosition> {
683    let mut peers: Vec<CellPosition> = self.get_cell_special_areas(cell, include_thermo)
684      .iter()
685      .flat_map(|area| self.get_area_cells(area))
686      .collect();
687
688    if self.constraints.anti_knight {
689      peers.extend(self.get_knight_peers(cell));
690    }
691
692    if self.constraints.anti_king {
693      peers.extend(self.get_king_peers(cell));
694    }
695
696    peers
697  }
698
699  // Returns peers that are special and are not peers through classical constraints
700  fn get_cell_only_special_peers(&self, cell: &CellPosition, include_thermo: bool) -> Vec<CellPosition> {
701    let special_peers = self.get_cell_special_peers(cell, include_thermo);
702    let classic_peers = self.get_cell_classic_peers(cell);
703
704    special_peers.into_iter().filter(|special_peer|
705      !classic_peers.contains(special_peer)
706    ).collect()
707  }
708
709  fn get_knight_peers(&self, cell: &CellPosition) -> Vec<CellPosition> {
710    KNIGHT_MOVES.iter().filter_map(|direction| {
711      let prow = cell.row as isize + direction.row;
712      let pcol = cell.col as isize + direction.col;
713      if prow < 0 || prow >= self.constraints.grid_size as isize ||
714         pcol < 0 || pcol >= self.constraints.grid_size as isize {
715        return None
716      }
717      let peer = CellPosition {
718        row: prow as usize,
719        col: pcol as usize,
720      };
721      Some(peer)
722    }).collect()
723  }
724
725  fn get_king_peers(&self, cell: &CellPosition) -> Vec<CellPosition> {
726    KING_MOVES.iter().filter_map(|direction| {
727      let prow = cell.row as isize + direction.row;
728      let pcol = cell.col as isize + direction.col;
729      if prow < 0 || prow >= self.constraints.grid_size as isize ||
730         pcol < 0 || pcol >= self.constraints.grid_size as isize {
731        return None
732      }
733      let peer = CellPosition {
734        row: prow as usize,
735        col: pcol as usize,
736      };
737      Some(peer)
738    }).collect()
739  }
740
741  fn is_empty_area_subset(&self, small_area: &Area, big_area: &Area) -> bool {
742    let small_set: HashSet<CellPosition> = self.get_empty_area_cells(small_area).into_iter().collect();
743    if small_set.is_empty() {
744      return false
745    }
746
747    let big_set: HashSet<CellPosition> = self.get_area_cells(big_area).into_iter().collect();
748    big_set.is_superset(&small_set)
749  }
750
751  fn get_subset_area_sum(&self, killer_cage: &KillerCage, big_area: &Area) -> u32 {
752    let big_set: HashSet<CellPosition> = self.get_area_cells(big_area).into_iter().collect();
753    let killer_cells_sum: u32 = killer_cage.region.iter().map(|&cell| {
754      if !big_set.contains(&cell) {
755        self.grid[cell.row][cell.col]
756      } else {
757        0
758      }
759    }).sum();
760    killer_cage.sum.unwrap() - killer_cells_sum
761  }
762
763  #[allow(dead_code)]
764  fn bit_mask_to_hash_set(&self, combination_mask: u32) -> HashSet<u32> {
765    (1..=self.constraints.grid_size).filter_map(|value| {
766      if combination_mask.bitand(1 << value) > 0 {
767        Some(value as u32)
768      } else {
769        None
770      }
771    }).collect()
772  }
773
774  fn arrow_circle_number(&self, arrow: &Arrow) -> (u32, bool) {
775    let mut value: u32 = 0;
776    let sorted_circle_cells: Vec<CellPosition> = arrow.circle_cells
777      .iter()
778      .cloned()
779      .sorted_by_key(|cell| *cell)
780      .collect();
781    let mut full = true;
782    for &CellPosition { row, col } in &sorted_circle_cells {
783      value = 10 * value + self.grid[row][col];
784      if self.grid[row][col] == 0 {
785        full = false;
786      }
787    }
788    (value, full)
789  }
790
791  fn arrow_arrow_sum(&self, arrow: &Arrow) -> (u32, bool) {
792    let mut sum: u32 = 0;
793    let mut full = true;
794    for &CellPosition { row, col } in &arrow.arrow_cells {
795      sum += self.grid[row][col];
796      if self.grid[row][col] == 0 {
797        full = false;
798      }
799    }
800    (sum, full)
801  }
802
803  fn count_empty_cells_in_list(&self, cells: &Vec<CellPosition>) -> usize {
804    cells.into_iter().filter(|cell| self.grid[cell.row][cell.col] == 0).count()
805  }
806}
807
808#[cfg(test)]
809mod tests;