lisudoku_solver/solver/
logical_solver.rs

1use std::collections::{HashSet, HashMap};
2use std::ops::BitOr;
3use std::rc::Rc;
4use crate::types::{Area, CellPosition, InvalidStateReason, SolutionStep, SolutionType, SudokulogicalSolveResult};
5use crate::solver::Solver;
6use self::combinations::cell_combination_logic::CellsCacheKey;
7use self::technique::Technique;
8use itertools::Itertools;
9
10pub mod technique;
11pub mod naked_singles;
12pub mod hidden_singles;
13pub mod thermo_steps;
14pub mod candidates;
15pub mod locked_candidates;
16pub mod naked_set;
17pub mod thermo_candidates;
18pub mod hidden_set;
19pub mod x_wing;
20pub mod xy_wing;
21pub mod common_peer_elimination;
22pub mod sum_candidates;
23pub mod killer_candidates;
24pub mod killer45;
25pub mod kropki_chain_candidates;
26pub mod kropki_advanced_candidates;
27pub mod common_peer_elimination_kropki;
28pub mod turbot_fish;
29pub mod top_bottom_candidates;
30pub mod empty_reclanges;
31pub mod combinations;
32pub mod arrow_candidates;
33pub mod advanced_candidates;
34pub mod arrow_advanced_candidates;
35pub mod common_peer_elimination_arrow;
36pub mod phistomefel_ring;
37pub mod nishio_forcing_chains;
38pub mod renban_candidates;
39pub mod palindrome_values;
40pub mod palindrome_candidates;
41pub mod adhoc_naked_set;
42
43const DEBUG: bool = false;
44const DISPLAY_STEPS: bool = false;
45
46impl Solver {
47  pub fn logical_solve(&mut self) -> SudokulogicalSolveResult {
48    let mut solution_type = SolutionType::Full;
49    let mut solution_steps = vec![];
50    let mut solution_steps_group_count = 0;
51
52    let check = self.check_partially_solved();
53    if !check.0 {
54      if DEBUG {
55        println!("Invalid initial grid");
56      }
57      return SudokulogicalSolveResult::no_solution(check.1.unwrap())
58    }
59
60    let mut empty_cell_count = self.compute_empty_cell_count();
61
62    while empty_cell_count > 0 {
63      // TODO: only check cells impacted by latest change
64      let check = self.check_partially_solved();
65      if !check.0 {
66        if DEBUG {
67          println!("Reached invalid state");
68        }
69        return SudokulogicalSolveResult::no_solution(check.1.unwrap())
70      }
71
72      // Some rules can find multiple steps at once
73      let mut steps = self.find_next_steps();
74      if steps.is_empty() {
75        break
76      }
77
78      solution_steps_group_count += 1;
79
80      if self.hint_mode {
81        // In hint mode apply 1 step at a time
82        steps.drain(1..);
83      }
84      if let Some(limit) = self.step_count_limit {
85        if solution_steps_group_count >= limit {
86          solution_steps.extend(steps);
87          // Found all steps from initial grid, stop
88          break
89        }
90      }
91
92      let mut grid_step = false;
93      for mut step in steps.into_iter() {
94        let rule_check = self.apply_rule(&mut step);
95        if !rule_check.0 {
96          return SudokulogicalSolveResult::no_solution(rule_check.1.unwrap())
97        }
98
99        if step.is_grid_step() {
100          grid_step = true;
101        }
102
103        solution_steps.push(step);
104      }
105
106      if self.hint_mode && grid_step {
107        // Found the first filled digit, it's enough for a hint
108        break
109      }
110
111      empty_cell_count = self.compute_empty_cell_count();
112    }
113
114    if empty_cell_count > 0 {
115      solution_type = SolutionType::Partial;
116    }
117
118    let res = SudokulogicalSolveResult {
119      solution_type,
120      solution: Some(self.grid.to_vec()),
121      steps: solution_steps.clone(),
122      invalid_state_reason: None,
123    };
124
125    res
126  }
127
128  fn find_next_steps(&self) -> Vec<SolutionStep> {
129    if self.hint_mode {
130      // In this context we already know that there is a valid solution
131      let steps = self.find_grid_steps();
132      if !steps.is_empty() {
133        return steps
134      }
135    }
136
137    // This type of rule must be 1st to make sure all candidates are valid
138    // before applying other techniques
139    let validity_update_steps = self.find_candidate_validity_update_steps();
140    // TODO: might want to tweak single_step_mode for validity_update_steps and nongrid_steps
141    if !validity_update_steps.is_empty() && self.step_count_limit.is_none() {
142      return validity_update_steps
143    }
144
145    let grid_steps = self.find_grid_steps();
146    if !grid_steps.is_empty() && self.step_count_limit.is_none() {
147      return grid_steps
148    }
149
150    let nongrid_steps = self.find_nongrid_steps();
151    if !nongrid_steps.is_empty() && self.step_count_limit.is_none() {
152      return nongrid_steps
153    }
154
155    vec![ validity_update_steps, grid_steps, nongrid_steps ].concat()
156  }
157
158  pub fn find_candidate_validity_update_steps(&self) -> Vec<SolutionStep> {
159    let candidate_validity_techniques: Vec<&Rc<dyn Technique>> = self.techniques
160      .iter()
161      .filter(|technique| technique.is_candidate_validity_update_step())
162      .collect();
163
164    let steps = self.run_techniques(candidate_validity_techniques);
165
166    steps
167  }
168
169  pub fn find_grid_steps(&self) -> Vec<SolutionStep> {
170    let grid_techniques: Vec<&Rc<dyn Technique>> = self.techniques
171      .iter()
172      .filter(|technique| technique.is_grid_step())
173      .collect();
174
175    self.run_techniques(grid_techniques)
176  }
177
178  fn find_nongrid_steps(&self) -> Vec<SolutionStep> {
179    let nongrid_techniques: Vec<&Rc<dyn Technique>> = self.techniques
180      .iter()
181      .filter(|technique| !technique.is_grid_step() &&
182                          !technique.is_candidate_validity_update_step())
183      .collect();
184
185    let steps = self.run_techniques(nongrid_techniques);
186
187    steps
188  }
189
190  fn run_techniques(&self, techniques: Vec<&Rc<dyn Technique>>) -> Vec<SolutionStep> {
191    techniques.into_iter().fold(vec![], |mut all_steps, technique| {
192      if !all_steps.is_empty() && self.step_count_limit.is_none() {
193        return all_steps
194      }
195      let steps = technique.run(&self);
196      all_steps.extend(steps);
197
198      all_steps
199    })
200  }
201
202  pub fn apply_rule(&mut self, step: &SolutionStep) -> (bool, Option<InvalidStateReason>) {
203    if DISPLAY_STEPS {
204      println!(
205        "{:?} ({}) ({}) ({}): {}",
206        step.rule,
207        step.areas.iter().map(|x| format!("{:?}", x)).join(", "),
208        step.cells.iter().map(|x| format!("({},{})", x.row, x.col)).join(" "),
209        step.values.iter().map(|x| format!("{}", x)).join(", "),
210        step.affected_cells.iter().map(|x| format!("({},{})", x.row, x.col)).join(" ")
211      );
212      if let Some(reason) = &step.invalid_state_reason {
213        println!("{:?}", reason);
214      }
215    }
216
217    let technique = self.techniques
218      .iter()
219      .find(|technique| technique.get_rule() == step.rule)
220      .cloned()
221      .unwrap_or_else(|| panic!("Can't find technique for rule {}", step.rule));
222
223    let rule_check = technique.apply(&step, self);
224
225    if DEBUG {
226      // only after cell changes
227      self.validate_candidates();
228    }
229
230    rule_check
231  }
232
233  pub fn apply_rules(&mut self, steps: &Vec<SolutionStep>) {
234    for mut step in steps {
235      self.apply_rule(&mut step);
236    }
237  }
238
239  // This method is used after placing a digit into the grid
240  // Returns cells that <cell> sees which have any of <values> candidates
241  fn get_affected_by_cell(&self, cell: &CellPosition, values: &HashSet<u32>) -> Vec<CellPosition> {
242    self.get_cell_peers_with_candidates(cell, values)
243  }
244
245  // Returns cells that are seen by all <cells> with any of <values> candidates
246  fn get_affected_by_cells(&self, cells: &Vec<CellPosition>, values: &HashSet<u32>) -> Vec<CellPosition> {
247    self.get_affected_by_cell(&cells[0], values)
248        .into_iter()
249        .filter(|cell| {
250          cells[1..].iter().all(|other_cell| {
251            self.cells_affect_eachother(cell, other_cell)
252          })
253        })
254        .collect()
255  }
256
257  fn cells_affect_eachother(&self, cell1: &CellPosition, cell2: &CellPosition) -> bool {
258    cell1 != cell2 &&
259    !self.get_cell_areas(cell1, false)
260         .into_iter()
261         .collect::<HashSet<Area>>()
262         .is_disjoint(
263           &self.get_cell_areas(cell2, false)
264                .into_iter()
265                .collect()
266         )
267  }
268
269  // Returns cells in area (given by <area_cells>) except <cells> that have any of <values> candidates
270  fn get_affected_by_area_cells_cells(&self, area_cells: &Vec<CellPosition>, cells: &Vec<CellPosition>, values: &HashSet<u32>) -> Vec<CellPosition> {
271    self.filter_cells_with_any_candidates(area_cells, values)
272        .into_iter()
273        .filter(|cell| !cells.contains(cell))
274        .collect()
275  }
276
277  fn update_candidates(&mut self, cells: &Vec<CellPosition>, value: u32) {
278    for cell in cells {
279      self.candidates[cell.row][cell.col].remove(&value);
280    }
281  }
282
283  fn compute_empty_cell_count(&self) -> usize {
284    self.grid
285        .iter()
286        .map(|row| row.iter()
287                      .map(|cell| if *cell == 0 { 1 } else { 0 })
288                      .sum::<usize>())
289        .sum()
290  }
291
292  fn find_common_areas_except(&self, cells: &Vec<CellPosition>, area_exception: Area) -> Vec<Area> {
293    let areas = self.find_common_areas(cells);
294    let other_areas: Vec<Area> = areas.into_iter().filter(|area| *area != area_exception).collect();
295    other_areas
296  }
297
298  // Note: update when adding new areas
299  // Returns all areas that contain all <cells>
300  // Used to see which areas are affected by all <cells>
301  // Note: we want areas that need all <grid_size> and unique values
302  fn find_common_areas(&self, cells: &Vec<CellPosition>) -> Vec<Area> {
303    assert!(cells.len() >= 2);
304
305    let cell1 = cells[0];
306
307    let mut areas = vec![];
308    if cells.iter().map(|cell| cell.row).all_equal() {
309      areas.push(Area::Row(cell1.row));
310    }
311    if cells.iter().map(|cell| cell.col).all_equal() {
312      areas.push(Area::Column(cell1.col));
313    }
314
315    let mut common_regions: HashSet<&usize> = self.grid_to_regions[cells[0].row][cells[0].col].iter().collect();
316    for cell in cells[1..].iter() {
317      let cell_regions: HashSet<&usize> = self.grid_to_regions[cell.row][cell.col].iter().collect();
318      common_regions = common_regions.intersection(&cell_regions).copied().collect();
319    }
320    for &region_index in common_regions.into_iter().sorted() {
321      areas.push(Area::Region(region_index));
322    }
323
324    if cells.iter().map(|cell| self.grid_to_killer_cage[cell.row][cell.col]).all_equal() {
325      let killer_cage_index = self.grid_to_killer_cage[cell1.row][cell1.col];
326      if killer_cage_index != usize::MAX {
327        areas.push(Area::KillerCage(killer_cage_index));
328      }
329    }
330    if self.constraints.primary_diagonal && cells.iter().all(|cell| cell.row == cell.col) {
331      areas.push(Area::PrimaryDiagonal);
332    }
333    if self.constraints.secondary_diagonal && cells.iter().all(|cell| cell.row + cell.col == self.constraints.grid_size - 1) {
334      areas.push(Area::SecondaryDiagonal);
335    }
336
337    let mut common_renbans: HashSet<&usize> = self.grid_to_renbans[cells[0].row][cells[0].col].iter().collect();
338    for cell in cells[1..].iter() {
339      let cell_renbans: HashSet<&usize> = self.grid_to_renbans[cell.row][cell.col].iter().collect();
340      common_renbans = common_renbans.intersection(&cell_renbans).copied().collect();
341    }
342    for &renban_index in common_renbans {
343      areas.push(Area::Renban(renban_index));
344    }
345
346    areas
347  }
348
349  fn any_cells_with_candidates(&self, cells: &Vec<CellPosition>, values: &HashSet<u32>) -> bool {
350    cells.iter().any(|cell| !self.candidates[cell.row][cell.col].is_disjoint(&values))
351  }
352
353  fn any_cells_with_other_candidates(&self, cells: &Vec<CellPosition>, values: &HashSet<u32>) -> bool {
354    cells.iter().any(|cell| self.candidates[cell.row][cell.col].difference(&values).count() > 0)
355  }
356
357  // Move <cell> orthogonally to <area>
358  fn cell_to_area(&self, cell: &CellPosition, area: &Area) -> CellPosition {
359    match area {
360      &Area::Row(row) => CellPosition { row, col: cell.col },
361      &Area::Column(col) => CellPosition { row: cell.row, col },
362      _ => unimplemented!(),
363    }
364  }
365
366  fn validate_candidates(&self) {
367    if !self.candidates_active {
368      return
369    }
370    for cell in &self.get_all_empty_cells() {
371      let &CellPosition { row, col } = cell;
372      let recomputed_cell_candidates = self.recompute_cell_candidates(cell);
373      if !self.candidates[row][col].is_subset(&recomputed_cell_candidates) {
374        println!("==> Invalid candidates for ({},{})!", row, col);
375        println!("Saved candidates: {:?}", self.candidates[row][col]);
376        println!("Real candidates: {:?}", recomputed_cell_candidates);
377        panic!();
378      }
379    }
380  }
381
382  pub fn cell_candidates_diff(&self, cells: &Vec<CellPosition>, valid_candidates: Vec<HashSet<u32>>) -> Vec<(CellPosition, Vec<u32>)> {
383    cells.into_iter().enumerate().filter_map(|(cell_index, &cell)| {
384      let cell_candidates = &self.candidates[cell.row][cell.col];
385      let valid_cell_candidates = &valid_candidates[cell_index];
386      if cell_candidates.len() == valid_cell_candidates.len() {
387        return None
388      }
389
390      let invalid_values: Vec<u32> = cell_candidates.difference(valid_cell_candidates)
391                                                    .into_iter()
392                                                    .copied()
393                                                    .sorted()
394                                                    .collect();
395
396      if invalid_values.is_empty() {
397        return None
398      }
399
400      Some((cell, invalid_values))
401    }).collect()
402  }
403
404  fn get_all_strong_links(&self) -> Vec<(Area, u32, CellPosition, CellPosition)> {
405    self.get_all_proper_areas().iter().flat_map(|area| {
406      let value_cells = self.compute_cells_by_value_in_area(area, &self.candidates);
407
408      value_cells.into_iter().sorted().filter_map(|(value, cells)| {
409        if cells.len() != 2 {
410          return None
411        }
412        return Some(
413          (area.clone(), value, cells[0], cells[1])
414        )
415      })
416    }).collect()
417  }
418
419  fn get_all_strong_links_by_value(&self) -> HashMap<u32, Vec<(Area, u32, CellPosition, CellPosition)>> {
420    self.get_all_strong_links()
421      .iter()
422      .cloned()
423      .sorted_by_key(|link| (link.1, link.0.clone(), link.2, link.3))
424      .group_by(|link| link.1)
425      .into_iter()
426      .map(|(value, group)| (value, group.collect()))
427      .collect()
428  }
429
430  fn candidates_to_set(&self, cell: CellPosition) -> u32 {
431    self.candidates[cell.row][cell.col].iter().fold(0, |acc, e| {
432      acc.bitor(1 << e)
433    })
434  }
435
436  fn cells_to_cache_key(&self, cells: &Vec<CellPosition>) -> CellsCacheKey {
437    cells.into_iter().map(|cell| {
438      (
439        cell.row as u32 * (self.constraints.grid_size as u32 + 1) + cell.col as u32,
440        self.grid[cell.row][cell.col],
441        self.candidates_to_set(*cell),
442      )
443    }).collect()
444  }
445}