sudoku_solver/
solver.rs

1pub mod fish;
2pub mod single_digit_patterns;
3use crate::sudoku::{CellIndex, CellValue, Step, StepKind, StepRule, Sudoku};
4use crate::utils::{CellSet, NamedCellSet};
5
6use std::cell::OnceCell;
7use std::collections::HashSet;
8
9use itertools::Itertools;
10use wasm_bindgen::prelude::*;
11
12#[wasm_bindgen]
13pub struct SudokuSolver {
14    sudoku: Sudoku,
15
16    all_constraints: Vec<NamedCellSet>,
17    constraints_of_cell: Vec<Vec<NamedCellSet>>,
18    house_union_of_cell: Vec<CellSet>,
19    block_id_of_cell: Vec<usize>,
20    row_id_of_cell: Vec<usize>,
21    column_id_of_cell: Vec<usize>,
22
23    cells_in_rows: Vec<NamedCellSet>,
24    cells_in_columns: Vec<NamedCellSet>,
25    cells_in_blocks: Vec<NamedCellSet>,
26    candidate_cells_in_rows: OnceCell<Vec<Vec<NamedCellSet>>>,
27    candidate_cells_in_columns: OnceCell<Vec<Vec<NamedCellSet>>>,
28    candidate_cells_in_blocks: OnceCell<Vec<Vec<NamedCellSet>>>,
29
30    possible_positions_for_house_and_value: Vec<OnceCell<NamedCellSet>>,
31}
32
33macro_rules! return_if_some {
34    ($x:expr) => {
35        if let Some(x) = $x {
36            return Some(x);
37        }
38    };
39}
40
41pub(crate) use return_if_some;
42
43impl SudokuSolver {
44    pub fn sudoku(&self) -> &Sudoku {
45        &self.sudoku
46    }
47
48    pub(crate) fn cell_index(&self, row: usize, col: usize) -> CellIndex {
49        self.sudoku.get_cell_position(row, col)
50    }
51
52    pub(crate) fn cell_value(&self, idx: CellIndex) -> Option<CellValue> {
53        self.sudoku.get_cell_value(idx)
54    }
55
56    pub(crate) fn candidates(&self, idx: CellIndex) -> &Vec<CellValue> {
57        self.sudoku.get_candidates(idx)
58    }
59
60    pub(crate) fn possible_cells(&self, value: CellValue) -> &CellSet {
61        self.sudoku.get_possible_cells(value)
62    }
63
64    pub(crate) fn can_fill(&self, idx: CellIndex, value: CellValue) -> bool {
65        self.sudoku.can_fill(idx, value)
66    }
67
68    pub(crate) fn all_constraints(&self) -> &[NamedCellSet] {
69        &self.all_constraints
70    }
71
72    pub(crate) fn constraints_of_cell(&self, idx: CellIndex) -> &[NamedCellSet] {
73        &self.constraints_of_cell[idx as usize]
74    }
75
76    pub(crate) fn house_union_of_cell(&self, idx: CellIndex) -> &CellSet {
77        &self.house_union_of_cell[idx as usize]
78    }
79
80    pub(crate) fn block_id_of_cell(&self, idx: CellIndex) -> usize {
81        self.block_id_of_cell[idx as usize]
82    }
83
84    pub(crate) fn cell_of_intersection(
85        &self,
86        house_1: &NamedCellSet,
87        house_2: &NamedCellSet,
88    ) -> CellIndex {
89        let (row_idx, col_idx) = if house_1.idx() < 18 {
90            debug_assert!(house_1.idx() >= 9);
91            debug_assert!(house_2.idx() >= 18);
92            debug_assert!(house_2.idx() < 27);
93            (house_1.idx() - 9, house_2.idx() - 18)
94        } else {
95            debug_assert!(house_2.idx() >= 9);
96            debug_assert!(house_1.idx() >= 18);
97            debug_assert!(house_1.idx() < 27);
98            (house_2.idx() - 9, house_1.idx() - 18)
99        };
100        return (row_idx * 9 + col_idx) as CellIndex;
101    }
102
103    pub(crate) fn row_id_of_cell(&self, idx: CellIndex) -> usize {
104        self.row_id_of_cell[idx as usize]
105    }
106
107    pub(crate) fn column_id_of_cell(&self, idx: CellIndex) -> usize {
108        self.column_id_of_cell[idx as usize]
109    }
110
111    pub(crate) fn cells_in_rows(&self) -> &[NamedCellSet] {
112        &self.cells_in_rows
113    }
114
115    pub(crate) fn cells_in_columns(&self) -> &[NamedCellSet] {
116        &self.cells_in_columns
117    }
118
119    pub(crate) fn cells_in_blocks(&self) -> &[NamedCellSet] {
120        &self.cells_in_blocks
121    }
122
123    pub(crate) fn candidate_cells_in_rows(&self, value: CellValue) -> &[NamedCellSet] {
124        &self.candidate_cells_in_rows.get_or_init(|| {
125            (1..=9)
126                .map(|value| {
127                    self.cells_in_rows
128                        .iter()
129                        .map(|row| {
130                            NamedCellSet::from_cellset(row, self.possible_cells(value) & row)
131                        })
132                        .collect()
133                })
134                .collect()
135        })[value as usize - 1]
136    }
137
138    pub(crate) fn candidate_cells_in_columns(&self, value: CellValue) -> &[NamedCellSet] {
139        &self.candidate_cells_in_columns.get_or_init(|| {
140            (1..=9)
141                .map(|value| {
142                    self.cells_in_columns
143                        .iter()
144                        .map(|col| {
145                            NamedCellSet::from_cellset(col, self.possible_cells(value) & col)
146                        })
147                        .collect()
148                })
149                .collect()
150        })[value as usize - 1]
151    }
152
153    pub(crate) fn candidate_cells_in_blocks(&self, value: CellValue) -> &[NamedCellSet] {
154        &self.candidate_cells_in_blocks.get_or_init(|| {
155            (1..=9)
156                .map(|value| {
157                    self.cells_in_blocks
158                        .iter()
159                        .map(|block| {
160                            NamedCellSet::from_cellset(block, self.possible_cells(value) & block)
161                        })
162                        .collect()
163                })
164                .collect()
165        })[value as usize - 1]
166    }
167
168    pub(crate) fn get_possible_cells_for_house_and_value(
169        &self,
170        house: &NamedCellSet,
171        value: CellValue,
172    ) -> &NamedCellSet {
173        debug_assert!(house.idx() < 27);
174        debug_assert!(value >= 1 && value <= 9);
175        let idx = house.idx() * 9 + value as usize - 1;
176        self.possible_positions_for_house_and_value[idx]
177            .get_or_init(|| NamedCellSet::from_cellset(house, self.possible_cells(value) & house))
178    }
179
180    pub(crate) fn get_cell_name(&self, idx: CellIndex) -> String {
181        format!("r{}c{}", idx / 9 + 1, idx % 9 + 1)
182    }
183
184    pub(crate) fn get_cellset_string(&self, cellset: &CellSet) -> String {
185        cellset.iter().map(|idx| self.get_cell_name(idx)).join(",")
186    }
187}
188
189#[wasm_bindgen]
190impl SudokuSolver {
191    pub fn new(sudoku: Sudoku) -> Self {
192        let mut all_constraints = vec![];
193        let mut constraints_of_cell = (0..81).map(|_| vec![]).collect::<Vec<_>>();
194        let mut house_union_of_cell = (0..81).map(|_| CellSet::new()).collect::<Vec<_>>();
195        let mut block_id_of_cell = vec![0; 81];
196        let mut row_id_of_cell = vec![0; 81];
197        let mut column_id_of_cell = vec![0; 81];
198        let mut cells_in_rows = vec![];
199        let mut cells_in_columns = vec![];
200        let mut cells_in_blocks = vec![];
201        let possible_positions_for_house_and_value = vec![OnceCell::new(); 27 * 9];
202
203        for block_x in (0..9).step_by(3) {
204            for block_y in (0..9).step_by(3) {
205                let mut block_set = NamedCellSet::new(
206                    format!("b{}", block_x + block_y / 3 + 1),
207                    block_x + block_y / 3,
208                );
209                for x in 0..3 {
210                    for y in 0..3 {
211                        let pos = sudoku.get_cell_position(block_x + x, block_y + y);
212                        block_set.add(pos);
213                        block_id_of_cell[pos as usize] = block_x + block_y / 3;
214                    }
215                }
216                all_constraints.push(block_set.clone());
217                cells_in_blocks.push(block_set);
218            }
219        }
220
221        for row in 0..9 {
222            let mut row_set = NamedCellSet::new(format!("r{}", row + 1), 9 + row);
223            for col in 0..9 {
224                let pos = sudoku.get_cell_position(row, col);
225                row_set.add(pos);
226                row_id_of_cell[pos as usize] = row;
227            }
228            all_constraints.push(row_set.clone());
229            cells_in_rows.push(row_set);
230        }
231
232        for col in 0..9 {
233            let mut col_set = NamedCellSet::new(format!("c{}", col + 1), 18 + col);
234            for row in 0..9 {
235                let pos = sudoku.get_cell_position(row, col);
236                col_set.add(pos);
237                column_id_of_cell[pos as usize] = col;
238            }
239            all_constraints.push(col_set.clone());
240            cells_in_columns.push(col_set);
241        }
242
243        for row in 0..9 {
244            for col in 0..9 {
245                let pos = sudoku.get_cell_position(row, col) as usize;
246                let block_x = row / 3;
247                let block_y = col / 3;
248                let block_idx = block_x * 3 + block_y;
249                constraints_of_cell[pos].push(cells_in_rows[row].clone());
250                constraints_of_cell[pos].push(cells_in_columns[col].clone());
251                constraints_of_cell[pos].push(cells_in_blocks[block_idx].clone());
252                house_union_of_cell[pos] =
253                    &(&cells_in_rows[row] | &cells_in_columns[col]) | &cells_in_blocks[block_idx];
254            }
255        }
256
257        SudokuSolver {
258            sudoku,
259
260            all_constraints,
261            constraints_of_cell,
262            house_union_of_cell,
263            block_id_of_cell,
264            row_id_of_cell,
265            column_id_of_cell,
266
267            cells_in_rows,
268            cells_in_columns,
269            cells_in_blocks,
270            candidate_cells_in_rows: OnceCell::new(),
271            candidate_cells_in_columns: OnceCell::new(),
272            candidate_cells_in_blocks: OnceCell::new(),
273
274            possible_positions_for_house_and_value,
275        }
276    }
277
278    pub fn get_invalid_positions(&self) -> Vec<CellIndex> {
279        let mut invalid_positions = vec![];
280        for house in self.all_constraints.iter() {
281            for (i, cell1) in house.iter().enumerate() {
282                if self.cell_value(cell1).is_none() {
283                    continue;
284                }
285                for cell2 in house.iter().take(i) {
286                    if cell1 == cell2 {
287                        invalid_positions.push(cell1);
288                    }
289                }
290            }
291        }
292        invalid_positions
293    }
294
295    pub fn initialize_candidates(&mut self) {
296        for cell in 0..81 {
297            if self.cell_value(cell).is_none() {
298                let mut candidates: HashSet<_> = (1..=9).collect();
299
300                for constraint in self.constraints_of_cell(cell).iter() {
301                    for other_cell in constraint.iter() {
302                        if cell == other_cell {
303                            continue;
304                        }
305                        if let Some(other_value) = self.cell_value(other_cell) {
306                            candidates.remove(&other_value);
307                        }
308                    }
309                }
310
311                for &candidate in candidates.iter().sorted() {
312                    self.sudoku.add_candidate(cell, candidate);
313                }
314            }
315        }
316    }
317
318    pub fn apply_step(&mut self, step: &Step) {
319        self.possible_positions_for_house_and_value = vec![OnceCell::new(); 27 * 9];
320        self.candidate_cells_in_rows.take();
321        self.candidate_cells_in_columns.take();
322        self.candidate_cells_in_blocks.take();
323        match step.kind {
324            StepKind::ValueSet => {
325                for position in step.positions.iter() {
326                    self.sudoku.fill(position.cell_index, position.value);
327                    for cell in self.house_union_of_cell[position.cell_index as usize].iter() {
328                        if cell == position.cell_index {
329                            continue;
330                        }
331                        self.sudoku.remove_candidate(cell, position.value);
332                    }
333                }
334            }
335            StepKind::CandidateEliminated => {
336                for position in step.positions.iter() {
337                    self.sudoku
338                        .remove_candidate(position.cell_index, position.value);
339                }
340            }
341        }
342    }
343
344    pub fn is_completed(&self) -> bool {
345        for cell in 0..81 {
346            if self.cell_value(cell).is_none() {
347                return false;
348            }
349        }
350        true
351    }
352
353    pub fn solve_full_house(&self) -> Option<Step> {
354        for house in self.all_constraints.iter() {
355            let unfilled_cells_count = house
356                .iter()
357                .filter(|&cell| self.cell_value(cell).is_none())
358                .count();
359            if unfilled_cells_count == 1 {
360                let unfilled_cell = house
361                    .iter()
362                    .filter(|&cell| self.cell_value(cell).is_none())
363                    .next()
364                    .unwrap();
365                let missing_value = self
366                    .candidates(unfilled_cell)
367                    .iter()
368                    .cloned()
369                    .next()
370                    .unwrap();
371                return Some(Step::new_value_set(
372                    StepRule::FullHouse,
373                    format!(
374                        "{} is the only missing cell in {}",
375                        self.get_cell_name(unfilled_cell),
376                        house.name()
377                    ),
378                    unfilled_cell,
379                    missing_value,
380                ));
381            }
382        }
383        None
384    }
385
386    pub fn solve_naked_single(&self) -> Option<Step> {
387        for house in self.all_constraints.iter() {
388            for cell in house.iter() {
389                if self.candidates(cell).len() == 1 {
390                    let &value = self.candidates(cell).iter().next().unwrap();
391                    return Some(Step::new_value_set(
392                        StepRule::NakedSingle,
393                        format!(
394                            "{} is the only possible value to fill {}",
395                            value,
396                            self.get_cell_name(cell)
397                        ),
398                        cell,
399                        value,
400                    ));
401                }
402            }
403        }
404        None
405    }
406
407    pub fn solve_hidden_single(&self) -> Option<Step> {
408        for house in self.all_constraints.iter() {
409            for value in 1..=9 {
410                let possible_cells = self.get_possible_cells_for_house_and_value(house, value);
411                if possible_cells.size() == 1 {
412                    let target_cell = possible_cells.iter().next().unwrap();
413                    return Some(Step::new_value_set(
414                        StepRule::HiddenSingle,
415                        format!(
416                            "in {}, {} is the only possible cell that can be {}",
417                            house.name(),
418                            self.get_cell_name(target_cell),
419                            value,
420                        ),
421                        target_cell,
422                        value,
423                    ));
424                }
425            }
426        }
427        None
428    }
429
430    // 当 House A 中的一个数字只出现在 House A & House B (A 和 B的交集)中时,这个数字不可能再出现在 House B 中的其他单元格中
431    pub fn solve_locked_candidates(&self) -> Option<Step> {
432        let check = |house_a: &NamedCellSet, house_b: &NamedCellSet| -> Option<Step> {
433            let intersection = house_a & house_b;
434            if intersection.is_empty() {
435                return None;
436            }
437
438            let mut step = Step::new(StepKind::CandidateEliminated, StepRule::LockedCandidates);
439
440            for value in 1..=9 {
441                let possible_cells_in_a =
442                    self.get_possible_cells_for_house_and_value(house_a, value);
443                if possible_cells_in_a.is_empty()
444                    || !possible_cells_in_a.is_subset_of(&intersection)
445                {
446                    continue;
447                }
448                for cell in house_b.iter() {
449                    if intersection.has(cell) {
450                        continue;
451                    }
452                    if self.can_fill(cell, value) {
453                        step.add(
454                            format!(
455                                "in {}, {} can only be in {} & {}",
456                                house_a.name(),
457                                value,
458                                house_a.name(),
459                                house_b.name(),
460                            ),
461                            cell,
462                            value,
463                        );
464                    }
465                }
466                if !step.is_empty() {
467                    return Some(step);
468                }
469            }
470            None
471        };
472
473        for block in &self.cells_in_blocks {
474            for row in &self.cells_in_rows {
475                let step = check(block, row);
476                if step.is_some() {
477                    return step;
478                }
479                let step = check(row, block);
480                if step.is_some() {
481                    return step;
482                }
483            }
484            for column in &self.cells_in_columns {
485                let step = check(block, column);
486                if step.is_some() {
487                    return step;
488                }
489                let step = check(column, block);
490                if step.is_some() {
491                    return step;
492                }
493            }
494        }
495        None
496    }
497
498    // 在一个 House 中,若任意 n 个数字只可能出现在相同 n 个(或更少)单元格中,则这 n 个单元格中不可能出现其他数字
499    pub fn solve_hidden_subset(&self) -> Option<Step> {
500        let mut step = Step::new(StepKind::CandidateEliminated, StepRule::HiddenSubset);
501
502        for house in self.all_constraints.iter() {
503            let mut possible_cells_in_houses = vec![];
504            for value in 1..=9 {
505                let possible_cells_in_house =
506                    self.get_possible_cells_for_house_and_value(house, value);
507                if !possible_cells_in_house.is_empty() {
508                    possible_cells_in_houses.push((value, possible_cells_in_house));
509                }
510            }
511
512            for size in 2..=4 {
513                let possible_house_cells_for_candidate_in_size: Vec<_> = possible_cells_in_houses
514                    .iter()
515                    .filter(|(_, cells)| cells.size() <= size)
516                    .collect();
517
518                if possible_house_cells_for_candidate_in_size.len() < size {
519                    continue;
520                }
521
522                for subset in possible_house_cells_for_candidate_in_size
523                    .iter()
524                    .combinations(size)
525                {
526                    let cell_union =
527                        CellSet::union_multiple(subset.iter().map(|(_, cells)| &***cells));
528                    let values_in_subset: HashSet<_> =
529                        subset.iter().map(|(value, _)| *value).collect();
530
531                    if cell_union.size() <= size {
532                        for cell in cell_union.iter() {
533                            for value in 1..=9 {
534                                if !values_in_subset.contains(&value) && self.can_fill(cell, value)
535                                {
536                                    step.add(
537                                        format!(
538                                            "in {}, {} only appears in {}",
539                                            house.name(),
540                                            values_in_subset.iter().sorted().join(","),
541                                            self.get_cellset_string(&cell_union),
542                                        ),
543                                        cell,
544                                        value,
545                                    );
546                                }
547                            }
548                        }
549                        if !step.is_empty() {
550                            return Some(step);
551                        }
552                    }
553                }
554            }
555        }
556        None
557    }
558
559    // 当一个 House 中的 n 个单元格只包含相同的 n 个(或更少)数字时,这 n 个数字不可能出现在这个 House 中的其他单元格中
560    pub fn solve_naked_subset(&self) -> Option<Step> {
561        for house in self.all_constraints.iter() {
562            for size in 2..=4 {
563                let mut step = Step::new(StepKind::CandidateEliminated, StepRule::NakedSubset);
564
565                for subset in house
566                    .iter()
567                    .filter(|&cell| {
568                        !self.candidates(cell).is_empty() && self.candidates(cell).len() <= size
569                    })
570                    .combinations(size)
571                {
572                    let value_union: HashSet<_> = subset
573                        .iter()
574                        .flat_map(|&cell| self.candidates(cell).iter().copied())
575                        .collect();
576                    let cells_in_subset = CellSet::from_cells(subset);
577
578                    if value_union.len() > size {
579                        continue;
580                    }
581
582                    for cell in house.iter() {
583                        if cells_in_subset.has(cell) {
584                            continue;
585                        }
586                        for &value in value_union.iter().sorted() {
587                            if self.can_fill(cell, value) {
588                                step.add(
589                                    format!(
590                                        "in {}, {} only contains {}",
591                                        house.name(),
592                                        self.get_cellset_string(&cells_in_subset),
593                                        value_union.iter().sorted().join(","),
594                                    ),
595                                    cell,
596                                    value,
597                                );
598                            }
599                        }
600                    }
601
602                    if !step.is_empty() {
603                        return Some(step);
604                    }
605                }
606            }
607        }
608        None
609    }
610
611    pub fn solve_one_step(&self) -> Option<Step> {
612        let solving_techniques = [
613            // SudokuSolver::solve_full_house,
614            SudokuSolver::solve_naked_single,
615            SudokuSolver::solve_hidden_single,
616            SudokuSolver::solve_locked_candidates,
617            SudokuSolver::solve_hidden_subset,
618            SudokuSolver::solve_naked_subset,
619            fish::solve_basic_fish,
620            fish::solve_finned_fish,
621            fish::solve_franken_fish,
622            // fish::solve_mutant_fish,
623        ];
624        for technique in solving_techniques.iter() {
625            if let Some(step) = technique(self) {
626                return Some(step);
627            }
628        }
629        None
630    }
631}