diff --git a/src/generator.rs b/src/generator.rs index 426af89..c6b3797 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,4 +1,4 @@ -use crate::grid::{Cell, Grid, CellValue, Line}; +use crate::grid::{Cell, Grid, CellValue, Section}; use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller, SolveStatistics}; use std::rc::Rc; use rand::prelude::*; @@ -61,7 +61,7 @@ impl Cell { fn calculate_possibilities(&self) -> Vec { // Need to calculate possibilities for this cell let mut possibilities = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; - fn eliminate_possibilities(possibilities: &mut Vec, line: &Line, cell: &Cell){ + fn eliminate_possibilities(possibilities: &mut Vec, line: &Section, cell: &Cell){ for (_index, other) in line.vec.iter().enumerate(){ if other.x != cell.x || other.y != cell.y { let value = &*other.value.borrow(); @@ -89,7 +89,7 @@ impl Cell { } } -impl Line { +impl Section { fn recalculate_and_set_possibilities(&self) { for (_index, cell) in self.vec.iter().enumerate() { let cell = &**cell; diff --git a/src/grid.rs b/src/grid.rs index 963dc47..651388c 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -10,16 +10,38 @@ pub enum CellValue { Unknown(Vec) } +/// A representation of a single cell in a Sudoku grid. Don't make this directly; make a Grid. pub struct Cell { pub x: usize, pub y: usize, pub value: RefCell, - pub row: Weak>, - pub column: Weak>, - pub section: Weak>, + pub row: Weak>, + pub column: Weak>, + pub section: Weak>, } impl Cell { + /// Set the `Cell`'s value to be a fixed digit. This method also removes the digit from any + /// affected cells in the same row, column, or square. + /// + /// # Examples + /// + /// ``` + /// use sudoku_solver::grid::{Grid, CellValue}; + /// let grid = Grid::new(); + /// + /// let cell1 = grid.get(0,0).unwrap(); + /// let cell2 = grid.get(0,1).unwrap(); + /// + /// assert_eq!(cell1.get_value_copy(), CellValue::Unknown(vec![1,2,3,4,5,6,7,8,9])); + /// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![1,2,3,4,5,6,7,8,9])); + /// + /// cell1.set(1); + /// + /// assert_eq!(cell1.get_value_copy(), CellValue::Fixed(1)); + /// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![2,3,4,5,6,7,8,9])); + /// + /// ``` pub fn set(&self, digit: u8){ unsafe { if DEBUG { @@ -46,11 +68,14 @@ impl Cell { Cell::process_possibilities(section, digit); } + /// Get a copy of the `CellValue` pub fn get_value_copy(&self) -> CellValue { let value = &*self.value.borrow(); return value.clone(); } + /// Set the cell value with a provided `CellValue`; if `value` is Fixed then the related cell's + /// possibilities are adjusted like in `set`. pub fn set_value(&self, value: CellValue){ match value { CellValue::Fixed(digit) => { @@ -63,6 +88,26 @@ impl Cell { } } + /// Set the `Cell`'s value to be a value **without** adjusting any of the nearby cells. + /// + /// # Examples + /// + /// ``` + /// use sudoku_solver::grid::{Grid, CellValue}; + /// let grid = Grid::new(); + /// + /// let cell1 = grid.get(0,0).unwrap(); + /// let cell2 = grid.get(0,1).unwrap(); + /// + /// assert_eq!(cell1.get_value_copy(), CellValue::Unknown(vec![1,2,3,4,5,6,7,8,9])); + /// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![1,2,3,4,5,6,7,8,9])); + /// + /// cell1.set_value_exact(CellValue::Fixed(1)); + /// + /// assert_eq!(cell1.get_value_copy(), CellValue::Fixed(1)); + /// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![1,2,3,4,5,6,7,8,9])); // still contains 1 + /// + /// ``` pub fn set_value_exact(&self, value: CellValue){ unsafe { if DEBUG { @@ -74,6 +119,7 @@ impl Cell { self.mark_updates(); } + /// Return a copy of the cell's possibilities if it has them. pub fn get_value_possibilities(&self) -> Option> { let value = &*self.value.borrow(); match value { @@ -82,6 +128,8 @@ impl Cell { } } + // Internal function - mark all the Sections the cell belongs to as having had a change + // so that the solver will look at it later fn mark_updates(&self){ { let row = &*self.row.upgrade().unwrap(); @@ -100,7 +148,8 @@ impl Cell { } } - fn process_possibilities(line: &Line, digit: u8){ + // Go through and remove digit from the Section's Cells' possibilities + fn process_possibilities(line: &Section, digit: u8){ for (_index, cell) in line.vec.iter().enumerate() { let cell = &**cell; @@ -143,38 +192,44 @@ impl Cell { } } -pub struct Line { +/// A representation of either a Row, Column, or Square in a Sudoku grid. Don't make this directly; make a Grid. +pub struct Section { + /// A vector of `Rc`s of the `Cell`s inside this Section. We use `Rc` because one of the + /// Sections needs to have ownership of the Cells but then the others have to have a different + /// signature. pub vec: Vec>, pub do_update: RefCell, pub index: usize, - pub line_type: LineType + pub section_type: SectionType } #[derive(Debug)] -pub enum LineType { +pub enum SectionType { Row, Column, - Section + Square } -impl Line { +impl Section { fn push(&mut self, x: Rc){ self.vec.push(x); } + /// Short-hand for accessing `vec` and calling it's `get` method. pub fn get(&self, index: usize) -> Option<&Rc>{ self.vec.get(index) } - fn new(index: usize, line_type: LineType) -> Line { - Line { + fn new(index: usize, line_type: SectionType) -> Section { + Section { vec: Vec::new(), do_update: RefCell::new(false), index, - line_type + section_type: line_type } } + /// Return a copy of whether this `Section` has been marked for the solver to work on it or not. pub fn do_update(&self) -> bool { let do_update = &self.do_update.borrow(); let do_update = &**do_update; @@ -185,23 +240,25 @@ impl Line { type MultiMut = Rc>; +/// A representation of a Sudoku grid. pub struct Grid { - pub rows: Vec>, // Read from top to bottom - pub columns: Vec>, - pub sections: Vec>, + pub rows: Vec>, // Read from top to bottom + pub columns: Vec>, + pub sections: Vec>, } impl Grid { + /// Generate a new empty `Grid` with full empty possibilities for each `Cell` pub fn new() -> Grid { - let mut rows: Vec> = Vec::new(); - let mut columns: Vec> = Vec::new(); - let mut sections: Vec> = Vec::new(); + let mut rows: Vec> = Vec::new(); + let mut columns: Vec> = Vec::new(); + let mut sections: Vec> = Vec::new(); for i in 0..9 { - rows.push(Rc::new(RefCell::new(Line::new(i, LineType::Row)))); - columns.push(Rc::new(RefCell::new(Line::new(i, LineType::Column)))); - sections.push(Rc::new(RefCell::new(Line::new(i, LineType::Section)))); + rows.push(Rc::new(RefCell::new(Section::new(i, SectionType::Row)))); + columns.push(Rc::new(RefCell::new(Section::new(i, SectionType::Column)))); + sections.push(Rc::new(RefCell::new(Section::new(i, SectionType::Square)))); } for row_index in 0..9 { @@ -248,24 +305,26 @@ impl Grid { return Grid { rows, columns, sections }; } - pub fn get(&self, r: usize, c: usize) -> Result, &str> { - if (r > 9) | (c > 9) { - return Err("Row or column indices are out of bounds"); - } + /// Returns the `Cell` (in an `Rc`) at the specified coordinates. + /// * `r` is the row coordinate (first row starting at 0) + /// * `c` is the column coordinate (first column starting at 0) + /// + /// Returns None if the coordinates are out of bounds. + pub fn get(&self, r: usize, c: usize) -> Option> { let row = match self.rows.get(r) { Some(x) => x, - None => {return Err("Row index is out of bounds")} + None => return None }; let row = &*(&**row).borrow(); let cell = match row.get(c) { Some(x) => x, - None => {return Err("Column index is out of bounds")} + None => return None }; - return Ok(Rc::clone(cell)); + return Some(Rc::clone(cell)); } fn process_unknown(x: &Vec, digit: u8, row: &mut String){ @@ -275,6 +334,32 @@ impl Grid { row.push(' '); } } + + /// Find the smallest empty `Cell` in terms of possibilities; returns `None` if all Cells have + /// `Fixed` `CellValue`s. + pub fn find_smallest_cell(&self) -> Option>{ + let mut smallest_cell : Option> = None; + let mut smallest_size = usize::MAX; + + for x in 0..9 { + for y in 0..9 { + let cell_rc = self.get(x, y).unwrap(); + let cell = &*self.get(x, y).unwrap(); + let cell_value = &*cell.value.borrow(); + + match cell_value { + CellValue::Unknown(possibilities) => { + if (possibilities.len() < smallest_size) && (possibilities.len() > 0){ + smallest_size = possibilities.len(); + smallest_cell = Some(cell_rc); + } + }, + _ => {} + } + } + } + smallest_cell + } } impl Clone for Grid { diff --git a/src/solver.rs b/src/solver.rs index 7974435..5aeaa6b 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::grid::{Cell, Line, Grid, CellValue}; +use crate::grid::{Cell, Section, Grid, CellValue}; pub static mut DEBUG: bool = false; @@ -17,7 +17,7 @@ pub enum SolveStatus { Invalid } - +/// See `SolveController` for a description of the solving strategies. enum SolveAction{ Single, HiddenSingle, @@ -54,12 +54,29 @@ impl SolveStatus { } } +/// A struct representing some options & solving strategies for solving a `Grid`. pub struct SolveController { + /// Whether the solver should try to determine if the solution is unique at the cost of extra computation. pub determine_uniqueness: bool, + + /// Whether the solving strategy where Cells with a single possibility are set to their value is enabled. + /// Has never been tested with a `false` value. pub search_singles: bool, + + /// Whether the solving strategy where Cells that contain as a digit a possibility that only occurs + /// once in that `Section` should be set to that value, is enabled. Has never been tested with a `false` value. pub search_hidden_singles: bool, + + /// Whether the solving strategy where, in a given `Section`, the solver tries to divide the un-set cells up + /// into two or more exclusive groups based on their possibilities, is enabled. pub find_possibility_groups: bool, + + /// Whether the solving strategy where if you know that a digit must occur in a part of Section A that overlaps + /// entirely with Section B, that you can then determine that that digit cannot occur in the rest of + /// Section B, is enabled. pub search_useful_constraint: bool, + + /// Whether the solver can make guesses as a last resort to try to solve the puzzle. pub make_guesses: bool, } @@ -89,10 +106,10 @@ impl SolveController { } } -/** - Tracks when we relied on a method to make progress. We'll consider 'relied on' to mean that the method make at least - one change to the line it was originally called on, whether that be setting a value or adjusting the possibilities in a cell. -*/ +/// Tracks how often we relied on each solving strategy to make progress. We'll consider 'relied on' to mean +/// that the method make at least one change to the line it was originally called on, whether that +/// be setting a value or adjusting the possibilities in a cell. Multiple contributions in one call +/// of the strategy on a `Section` are only counted as one contribution. #[derive(Copy, Clone)] pub struct SolveStatistics { pub singles: u32, @@ -103,7 +120,9 @@ pub struct SolveStatistics { } impl SolveStatistics { - pub(crate) fn new() -> SolveStatistics { + + /// Create a new SolveStatistics with `0` counts set for all the fields. + pub fn new() -> SolveStatistics { SolveStatistics{ singles: 0, hidden_singles: 0, @@ -124,43 +143,13 @@ impl SolveStatistics { } } -pub fn find_smallest_cell(grid: &Grid) -> Option>{ - // Find a cell of smallest size (in terms of possibilities) and make a guess - // Can assume that no cells of only possibility 1 exist - let mut smallest_cell : Option> = None; - let mut smallest_size = usize::MAX; - 'outer: for x in 0..9 { - for y in 0..9 { - let cell_rc = grid.get(x, y).unwrap(); - let cell = &*grid.get(x, y).unwrap(); - let cell_value = &*cell.value.borrow(); - - match cell_value { - CellValue::Unknown(possibilities) => { - if (possibilities.len() < smallest_size) && (possibilities.len() > 0){ - smallest_size = possibilities.len(); - smallest_cell = Some(cell_rc); - } - }, - _ => {} - } - - if smallest_size <= 2 { - break 'outer; // We aren't going to get smaller - } - - } - } - smallest_cell - -} // Code for identify_and_process_possibility_groups (it uses it's own structs) mod process_possibility_groups { - use crate::grid::{Line, CellValue}; + use crate::grid::{Section, CellValue}; use std::collections::HashSet; use std::rc::Rc; @@ -194,18 +183,18 @@ mod process_possibility_groups { } // See if there's a set of cells with possibilities that exclude those possibilities from other cells. -// Runs recursively on each group to identify all groups in case there's more than 2. - pub fn identify_and_process_possibility_groups(line: &Line) -> bool{ + // Runs recursively on each group to identify all groups in case there's more than 2. + pub fn identify_and_process_possibility_groups(line: &Section) -> bool{ unsafe { if super::DEBUG { - println!("Looking for possibility groups on line {:?} {}", line.line_type, line.index); + println!("Looking for possibility groups on line {:?} {}", line.section_type, line.index); } } bisect_possibility_groups(line, vec![0, 1, 2, 3, 4, 5, 6, 7, 8]) } - fn bisect_possibility_groups(line: &Line, cells_of_interest: Vec) -> bool{ + fn bisect_possibility_groups(line: &Section, cells_of_interest: Vec) -> bool{ /* Algorithm - @@ -381,10 +370,10 @@ mod process_possibility_groups { } // Search for a cell with only one possibility so that we can set it to FIXED -fn search_single_possibility(line: &Line) -> bool{ +fn search_single_possibility(line: &Section) -> bool{ unsafe { if DEBUG { - println!("search_single_possibility on line {:?} {}", line.line_type, line.index); + println!("search_single_possibility on line {:?} {}", line.section_type, line.index); } } @@ -407,7 +396,7 @@ fn search_single_possibility(line: &Line) -> bool{ } // Count up how many times each possibility occurs in the Line. If it only occurs once, that's a hidden single that we can set -fn search_hidden_single(line: &Line) -> bool{ +fn search_hidden_single(line: &Section) -> bool{ enum Count { None, One(Rc), @@ -456,7 +445,7 @@ fn search_hidden_single(line: &Line) -> bool{ } mod search_useful_constraint{ - use crate::grid::{Grid, Line, LineType, CellValue}; + use crate::grid::{Grid, Section, SectionType, CellValue}; use std::rc::{Rc, Weak}; use std::cell::RefCell; @@ -479,19 +468,19 @@ mod search_useful_constraint{ // I.e. If possibility '1' only occurs in the first row for section 0, then you can remove that possibility // from row 0 across the other sections. Conversely, if the possibility only occurs in the first section // for row 0, then you can remove the possibility from the rest of section 0. - pub fn search_useful_constraint(grid: &Grid, line: &Line) -> bool{ + pub fn search_useful_constraint(grid: &Grid, line: &Section) -> bool{ unsafe { if super::DEBUG { - println!("Searching for a useful constraint on line {:?} {}", line.line_type, line.index); + println!("Searching for a useful constraint on line {:?} {}", line.section_type, line.index); } } let mut made_change = false; - let (check_row, check_column, check_section) = match line.line_type { - LineType::Row => {(false, false, true)}, - LineType::Column => {(false, false, true)}, - LineType::Section => {(true, true, false)}, + let (check_row, check_column, check_section) = match line.section_type { + SectionType::Row => {(false, false, true)}, + SectionType::Column => {(false, false, true)}, + SectionType::Square => {(true, true, false)}, }; for possibility in 0..9 { @@ -532,21 +521,21 @@ mod search_useful_constraint{ match rows { PossibilityLines::Unique(index) => { made_change = made_change | - remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.line_type, line.index); + remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.section_type, line.index); }, _ => {} } match columns { PossibilityLines::Unique(index) => { made_change = made_change | - remove_possibilities_line(grid.columns.get(index).unwrap(), possibility, &line.line_type, line.index); + remove_possibilities_line(grid.columns.get(index).unwrap(), possibility, &line.section_type, line.index); }, _ => {} } match sections { PossibilityLines::Unique(index) => { made_change = made_change | - remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.line_type, line.index); + remove_possibilities_line(grid.sections.get(index).unwrap(), possibility, &line.section_type, line.index); }, _ => {} } @@ -557,7 +546,7 @@ mod search_useful_constraint{ } // initial_line_type and initial_line_index are to identify the cells that should NOT have their possibilities removed - fn remove_possibilities_line(line: &Rc>, digit_to_remove: u8, initial_line_type: &LineType, initial_line_index: usize) -> bool { + fn remove_possibilities_line(line: &Rc>, digit_to_remove: u8, initial_line_type: &SectionType, initial_line_index: usize) -> bool { let line = &*(&**line).borrow(); let mut made_change = false; @@ -567,9 +556,9 @@ mod search_useful_constraint{ match value { CellValue::Unknown(possibilities) => { let parent_line = match initial_line_type { - LineType::Row => &cell.row, - LineType::Column => &cell.column, - LineType::Section => &cell.section + SectionType::Row => &cell.row, + SectionType::Column => &cell.column, + SectionType::Square => &cell.section }; let parent_line = &*parent_line.upgrade().unwrap(); let parent_line = &*parent_line.borrow(); @@ -609,7 +598,7 @@ mod search_useful_constraint{ } // We detected a useful constraint - fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak>) -> PossibilityLines { + fn process_possibility_line(possibility_line: PossibilityLines, line: &Weak>) -> PossibilityLines { let line = line.upgrade().unwrap(); let line = &*(&*line).borrow(); @@ -629,10 +618,10 @@ mod search_useful_constraint{ } -fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics){ +fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics){ unsafe { if DEBUG { - println!("Solving {:?} {}", line.line_type, line.index); + println!("Solving {:?} {}", line.section_type, line.index); } } @@ -641,7 +630,7 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController, solv if solve_controller.search_singles() { unsafe { if DEBUG { - println!("Searching for singles on line {:?} of {}\n{}", line.line_type, line.index, grid); + println!("Searching for singles on line {:?} of {}\n{}", line.section_type, line.index, grid); } } if search_single_possibility(line) { @@ -652,7 +641,7 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController, solv if solve_controller.search_hidden_singles() { unsafe { if DEBUG { - println!("Searching for hidden singles on line {:?} of {}\n{}", line.line_type, line.index, grid); + println!("Searching for hidden singles on line {:?} of {}\n{}", line.section_type, line.index, grid); } } if search_hidden_single(line) { @@ -663,7 +652,7 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController, solv if solve_controller.find_possibility_groups() { unsafe { if DEBUG { - println!("Searching for possibility groups on line {:?} of {}\n{}", line.line_type, line.index, grid); + println!("Searching for possibility groups on line {:?} of {}\n{}", line.section_type, line.index, grid); } } if process_possibility_groups::identify_and_process_possibility_groups(line) { @@ -674,7 +663,7 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController, solv if solve_controller.search_useful_constraint() { unsafe { if DEBUG { - println!("Searching for useful constraints on line {:?} of {}\n{}", line.line_type, line.index, grid); + println!("Searching for useful constraints on line {:?} of {}\n{}", line.section_type, line.index, grid); } } if search_useful_constraint::search_useful_constraint(grid, line) { @@ -684,6 +673,9 @@ fn solve_line(grid: &Grid, line: &Line, solve_controller: &SolveController, solv } +/// Solves (and modifies) the input `Grid`. Returns a `SolveStatus` and `SolveStatistics`, and +/// enables all solving strategies. If you want to specify a `SolveController` you can +/// call `solve_grid_with_solve_controller` directly, but you also have to input an empty `SolveStatistics`. pub fn solve_grid(grid: &mut Grid) -> (SolveStatus, SolveStatistics) { // By default we enable everything let solve_controller = SolveController { @@ -701,6 +693,7 @@ pub fn solve_grid(grid: &mut Grid) -> (SolveStatus, SolveStatistics) { return (solve_status, solve_statistics); } +/// Solves (and modifies) the input `Grid` & `SolveStatistics`. Returns a `SolveStatus`. pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{ // Code is kind of messy so here it goes - solve_grid first tries to solve without any guesses // If that's not enough and a guess is required, then solve_grid_guess is called @@ -724,7 +717,9 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv return status; } -// Similar to solve_grid_with_solve_controller except that we don't modify the input Grid; we only determine SolveStatus +/// Similar to `solve_grid_with_solve_controller` except that we don't modify the input Grid; we +/// only determine SolveStatus. This is useful when generating puzzles where we need to know *if* +/// a puzzle can be solved but don't want to actually solve it. pub fn evaluate_grid_with_solve_controller(grid: &Grid, solve_controller: &SolveController) -> (SolveStatus, SolveStatistics){ let mut mut_grid = grid.clone(); let mut solve_statistics = SolveStatistics::new(); @@ -734,7 +729,7 @@ pub fn evaluate_grid_with_solve_controller(grid: &Grid, solve_controller: &Solve return (solve_status, solve_statistics); } -pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{ +fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{ loop { let mut ran_something = false; @@ -800,7 +795,7 @@ pub fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{ solve_statistics.increment(&SolveAction::Guess); - let smallest_cell = find_smallest_cell(grid); + let smallest_cell = grid.find_smallest_cell(); let smallest_cell = match smallest_cell { Some(cell) => cell, None => return SolveStatus::Invalid