From 8dc7954f53c1b4c3198250991cc4760d7de89e60 Mon Sep 17 00:00:00 2001 From: Joel Therrien Date: Tue, 29 Sep 2020 13:45:48 -0700 Subject: [PATCH] Run cargo fmt --- src/bin/generator.rs | 189 ++++++++++------ src/bin/solver.rs | 24 +- src/generator.rs | 187 +++++++++------- src/grid.rs | 103 ++++----- src/lib.rs | 6 +- src/pdf.rs | 40 ++-- src/solver.rs | 506 +++++++++++++++++++++++++++---------------- 7 files changed, 642 insertions(+), 413 deletions(-) diff --git a/src/bin/generator.rs b/src/bin/generator.rs index cb6ff39..87e1b75 100644 --- a/src/bin/generator.rs +++ b/src/bin/generator.rs @@ -1,13 +1,13 @@ use rand::prelude::*; -use sudoku_solver::grid::{Grid, CellValue}; use std::error::Error; use std::io::Write; -use sudoku_solver::solver::{SolveController, SolveStatistics}; -use std::str::FromStr; -use std::sync::{mpsc, Arc}; use std::process::exit; -use std::thread; +use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc}; +use std::thread; +use sudoku_solver::grid::{CellValue, Grid}; +use sudoku_solver::solver::{SolveController, SolveStatistics}; /* We have to be very careful here because Grid contains lots of Rcs and RefCells which could enable mutability @@ -24,28 +24,28 @@ enum Difficulty { Challenge, Hard, Medium, - Easy + Easy, } impl Difficulty { fn map_to_solve_controller(&self) -> SolveController { - let mut controller = SolveController{ + let mut controller = SolveController { determine_uniqueness: true, search_singles: true, search_hidden_singles: true, find_possibility_groups: true, search_useful_constraint: true, - make_guesses: true + make_guesses: true, }; match self { - Difficulty::Challenge => {}, // Do nothing, already hard + Difficulty::Challenge => {} // Do nothing, already hard Difficulty::Hard => { controller.make_guesses = false; } Difficulty::Medium => { controller.make_guesses = false; - }, + } Difficulty::Easy => { controller.make_guesses = false; controller.search_useful_constraint = false; @@ -59,31 +59,35 @@ impl Difficulty { fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool { match self { Difficulty::Challenge => { - (solve_statistics.guesses > 0) && (solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20) + (solve_statistics.guesses > 0) + && (solve_statistics.possibility_groups > 20) + && (solve_statistics.useful_constraints > 20) } Difficulty::Hard => { - (solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20) + (solve_statistics.possibility_groups > 20) + && (solve_statistics.useful_constraints > 20) } Difficulty::Medium => { - (solve_statistics.possibility_groups > 10) && (solve_statistics.useful_constraints > 10) + (solve_statistics.possibility_groups > 10) + && (solve_statistics.useful_constraints > 10) } - Difficulty::Easy => {true} // easy has no minimum + Difficulty::Easy => true, // easy has no minimum } } } -impl FromStr for Difficulty { // Needed for argparse +impl FromStr for Difficulty { + // Needed for argparse type Err = String; fn from_str(s: &str) -> Result { - - if s.eq_ignore_ascii_case("EASY"){ + if s.eq_ignore_ascii_case("EASY") { return Ok(Difficulty::Easy); - } else if s.eq_ignore_ascii_case("MEDIUM"){ + } else if s.eq_ignore_ascii_case("MEDIUM") { return Ok(Difficulty::Medium); - } else if s.eq_ignore_ascii_case("HARD"){ + } else if s.eq_ignore_ascii_case("HARD") { return Ok(Difficulty::Hard); - } else if s.eq_ignore_ascii_case("CHALLENGE"){ + } else if s.eq_ignore_ascii_case("CHALLENGE") { return Ok(Difficulty::Challenge); } @@ -92,50 +96,75 @@ impl FromStr for Difficulty { // Needed for argparse } fn main() { - let mut debug = false; let mut max_hints = 81; let mut max_attempts = 100; - let mut filename : Option = None; + let mut filename: Option = None; let mut difficulty = Difficulty::Challenge; let mut threads = 1; - { // this block limits scope of borrows by ap.refer() method + { + // this block limits scope of borrows by ap.refer() method let mut ap = argparse::ArgumentParser::new(); ap.set_description("Generate Sudoku puzzles"); ap.refer(&mut debug) .add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode"); - ap.refer(&mut max_hints) - .add_option(&["--hints"], argparse::Store, "Only return a puzzle with less than or equal to this number of hints"); + ap.refer(&mut max_hints).add_option( + &["--hints"], + argparse::Store, + "Only return a puzzle with less than or equal to this number of hints", + ); ap.refer(&mut max_attempts) .add_option(&["--attempts"], argparse::Store, "Number of puzzles each thread will generate to find an appropriate puzzle; default is 100"); - ap.refer(&mut filename) - .add_argument("filename", argparse::StoreOption, "Optional filename to store puzzle in as a CSV"); + ap.refer(&mut filename).add_argument( + "filename", + argparse::StoreOption, + "Optional filename to store puzzle in as a CSV", + ); - ap.refer(&mut difficulty) - .add_option(&["-d", "--difficulty"], argparse::Store, "Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE"); + ap.refer(&mut difficulty).add_option( + &["-d", "--difficulty"], + argparse::Store, + "Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE", + ); - ap.refer(&mut threads) - .add_option(&["--threads"], argparse::Store, "Number of threads to use when generating possible puzzles"); + ap.refer(&mut threads).add_option( + &["--threads"], + argparse::Store, + "Number of threads to use when generating possible puzzles", + ); ap.parse_args_or_exit(); } let solve_controller = difficulty.map_to_solve_controller(); - let (result, num_attempts) = - if threads < 1 { - eprintln!("--threads must be at least 1"); - exit(1); - } else if threads == 1 { - let mut rng = SmallRng::from_entropy(); - get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, max_attempts, max_hints, &AtomicBool::new(false)) - } else { - run_multi_threaded(max_attempts, max_hints, threads, debug, solve_controller, difficulty) - }; + let (result, num_attempts) = if threads < 1 { + eprintln!("--threads must be at least 1"); + exit(1); + } else if threads == 1 { + let mut rng = SmallRng::from_entropy(); + get_puzzle_matching_conditions( + &mut rng, + &difficulty, + &solve_controller, + max_attempts, + max_hints, + &AtomicBool::new(false), + ) + } else { + run_multi_threaded( + max_attempts, + max_hints, + threads, + debug, + solve_controller, + difficulty, + ) + }; let (grid, solve_statistics, num_hints) = match result { Some(x) => x, @@ -145,16 +174,27 @@ fn main() { } }; - println!("{}", grid); - println!("Puzzle has {} hints and was found in {} attempts.", num_hints, num_attempts); + println!( + "Puzzle has {} hints and was found in {} attempts.", + num_hints, num_attempts + ); if debug { println!("Solving this puzzle involves roughly:"); println!("\t{} SINGLE actions", solve_statistics.singles); - println!("\t{} HIDDEN_SINGLE actions", solve_statistics.hidden_singles); - println!("\t{} USEFUL_CONSTRAINT actions", solve_statistics.useful_constraints); - println!("\t{} POSSIBILITY_GROUP actions", solve_statistics.possibility_groups); + println!( + "\t{} HIDDEN_SINGLE actions", + solve_statistics.hidden_singles + ); + println!( + "\t{} USEFUL_CONSTRAINT actions", + solve_statistics.useful_constraints + ); + println!( + "\t{} POSSIBILITY_GROUP actions", + solve_statistics.possibility_groups + ); println!("\t{} GUESS actions", solve_statistics.guesses); } @@ -164,17 +204,23 @@ fn main() { if filename.ends_with(".pdf") { sudoku_solver::pdf::draw_grid(&grid, &filename).unwrap(); println!("Grid saved as pdf to {}", filename); - } else{ + } else { save_grid_csv(&grid, &filename).unwrap(); println!("Grid saved as CSV to {}", filename); } - }, + } None => {} } - } -fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bool, solve_controller: SolveController, difficulty: Difficulty) -> (Option<(Grid, SolveStatistics, i32)>, i32){ +fn run_multi_threaded( + max_attempts: i32, + max_hints: i32, + threads: i32, + debug: bool, + solve_controller: SolveController, + difficulty: Difficulty, +) -> (Option<(Grid, SolveStatistics, i32)>, i32) { let mut thread_rng = thread_rng(); let (transmitter, receiver) = mpsc::channel(); let mut remaining_attempts = max_attempts; @@ -195,11 +241,18 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo } let should_stop = &*should_stop; - let (result, num_attempts) = get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, thread_attempts, max_hints, should_stop); + let (result, num_attempts) = get_puzzle_matching_conditions( + &mut rng, + &difficulty, + &solve_controller, + thread_attempts, + max_hints, + should_stop, + ); let mut result_was_some = false; let result = match result { - None => {None} + None => None, Some((grid, solve_statistics, num_hints)) => { result_was_some = true; Some((SafeGridWrapper(grid), solve_statistics, num_hints)) @@ -209,7 +262,10 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo cloned_transmitter.send((result, num_attempts)).unwrap(); if debug { - println!("Thread {}, terminated having run {} attempts; did send result: {}", i, num_attempts, result_was_some); + println!( + "Thread {}, terminated having run {} attempts; did send result: {}", + i, num_attempts, result_was_some + ); } }); } @@ -220,7 +276,7 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo while threads_running > 0 { let signal = receiver.recv().unwrap(); // Not sure what errors can result here but they are unexpected and deserve a panic - threads_running-=1; + threads_running -= 1; let (result, attempts) = signal; attempt_count += attempts; @@ -237,12 +293,19 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo return (result_to_return, attempt_count); } -fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, solve_controller: &SolveController, max_attempts: i32, max_hints: i32, should_stop: &AtomicBool) -> (Option<(Grid, SolveStatistics, i32)>, i32){ +fn get_puzzle_matching_conditions( + rng: &mut SmallRng, + difficulty: &Difficulty, + solve_controller: &SolveController, + max_attempts: i32, + max_hints: i32, + should_stop: &AtomicBool, +) -> (Option<(Grid, SolveStatistics, i32)>, i32) { let mut num_attempts = 0; - while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed){ - - let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(rng, &solve_controller); + while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed) { + let (grid, num_hints, solve_statistics) = + sudoku_solver::generator::generate_grid(rng, &solve_controller); num_attempts += 1; if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints { @@ -253,7 +316,7 @@ fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, s return (None, num_attempts); } -fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box>{ +fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box> { // Not using the csv crate for writing because it's being difficult and won't accept raw integers let mut file = std::fs::File::create(filename)?; @@ -261,10 +324,9 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box>{ for y in 0..9 { let cell = grid.get(x, y).unwrap(); let value = &*cell.value.borrow(); - let digit = - match value { - CellValue::Fixed(digit) => {*digit} - CellValue::Unknown(_) => {0} + let digit = match value { + CellValue::Fixed(digit) => *digit, + CellValue::Unknown(_) => 0, }; let mut text = digit.to_string(); @@ -272,7 +334,6 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box>{ text.push(','); } file.write(text.as_bytes())?; - } file.write(b"\n")?; } diff --git a/src/bin/solver.rs b/src/bin/solver.rs index f3e3032..a787201 100644 --- a/src/bin/solver.rs +++ b/src/bin/solver.rs @@ -3,19 +3,21 @@ use std::str::FromStr; use sudoku_solver::grid::Grid; use sudoku_solver::solver::solve_grid; - fn main() { let mut debug = false; let mut filename = String::new(); - { // this block limits scope of borrows by ap.refer() method + { + // this block limits scope of borrows by ap.refer() method let mut ap = argparse::ArgumentParser::new(); ap.set_description("Solve Sudoku puzzles"); ap.refer(&mut debug) .add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode"); - ap.refer(&mut filename) - .required() - .add_argument("filename", argparse::Store, "Path to puzzle CSV file"); + ap.refer(&mut filename).required().add_argument( + "filename", + argparse::Store, + "Path to puzzle CSV file", + ); ap.parse_args_or_exit(); } @@ -27,7 +29,6 @@ fn main() { } } - let mut grid = match read_grid(&filename) { Ok(grid) => grid, Err(e) => { @@ -42,8 +43,6 @@ fn main() { solve_grid(&mut grid); println!("Solved grid:\n{}", grid); - - } fn read_grid(filename: &str) -> Result { @@ -78,18 +77,15 @@ fn read_grid(filename: &str) -> Result { if digit > 0 { grid.get(row, column).unwrap().set(digit); } - - }, - Err(_error) => {return Err("Invalid cell value".to_string())} + } + Err(_error) => return Err("Invalid cell value".to_string()), }; - - }, + } None => {} } } row = row + 1; - } return Ok(grid); diff --git a/src/generator.rs b/src/generator.rs index 5c01d1c..9ea10f0 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,12 +1,14 @@ -use crate::grid::{Cell, Grid, CellValue, Section}; -use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller, SolveStatistics}; -use std::rc::Rc; +use crate::grid::{Cell, CellValue, Grid, Section}; +use crate::solver::{ + evaluate_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness, +}; use rand::prelude::*; +use std::rc::Rc; -pub static mut DEBUG : bool = false; +pub static mut DEBUG: bool = false; impl Grid { - fn get_random_empty_cell(&self, rng : &mut SmallRng) -> Result, &str> { + fn get_random_empty_cell(&self, rng: &mut SmallRng) -> Result, &str> { // Idea - put all empty cells into a vector and choose one at random // If vector is empty we return an error @@ -16,11 +18,10 @@ impl Grid { let cell = self.get(x, y).unwrap(); let add_cell = { let cell_value = &*cell.value.borrow(); - match cell_value { // May cause issues with borrow rules - CellValue::Fixed(_) => {false} - CellValue::Unknown(_) => { - true - } + match cell_value { + // May cause issues with borrow rules + CellValue::Fixed(_) => false, + CellValue::Unknown(_) => true, } }; if add_cell { @@ -31,13 +32,13 @@ impl Grid { match empty_cells.iter().choose(rng) { Some(cell) => Ok(Rc::clone(cell)), - None => Err("Unable to find an empty cell") + None => Err("Unable to find an empty cell"), } } } impl Cell { - fn delete_value(&self){ + fn delete_value(&self) { unsafe { if DEBUG { println!("Cell {}, {} had its value deleted.", self.x, self.y); @@ -47,10 +48,21 @@ impl Cell { self.set_value_exact(CellValue::Unknown(vec![])); // placeholder // This will reset all the possibilities for this cell and the ones that might have been limited by this cell - self.section.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); - self.row.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); - self.column.upgrade().unwrap().borrow().recalculate_and_set_possibilities(); - + self.section + .upgrade() + .unwrap() + .borrow() + .recalculate_and_set_possibilities(); + self.row + .upgrade() + .unwrap() + .borrow() + .recalculate_and_set_possibilities(); + self.column + .upgrade() + .unwrap() + .borrow() + .recalculate_and_set_possibilities(); } /** @@ -60,8 +72,8 @@ 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: &Section, cell: &Cell){ - for (_index, other) in line.vec.iter().enumerate(){ + 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(); match value { @@ -80,9 +92,21 @@ impl Cell { } } - eliminate_possibilities(&mut possibilities, &self.section.upgrade().unwrap().borrow(), self); - eliminate_possibilities(&mut possibilities, &self.row.upgrade().unwrap().borrow(), self); - eliminate_possibilities(&mut possibilities, &self.column.upgrade().unwrap().borrow(), self); + eliminate_possibilities( + &mut possibilities, + &self.section.upgrade().unwrap().borrow(), + self, + ); + eliminate_possibilities( + &mut possibilities, + &self.row.upgrade().unwrap().borrow(), + self, + ); + eliminate_possibilities( + &mut possibilities, + &self.column.upgrade().unwrap().borrow(), + self, + ); return possibilities; } @@ -95,10 +119,10 @@ impl Section { let new_possibilities = { let cell_value = &*cell.value.borrow(); match cell_value { - CellValue::Fixed(_) => { continue; } - CellValue::Unknown(_) => { - cell.calculate_possibilities() + CellValue::Fixed(_) => { + continue; } + CellValue::Unknown(_) => cell.calculate_possibilities(), } }; @@ -107,8 +131,10 @@ impl Section { } } -pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) -> (Grid, i32, SolveStatistics) { - +pub fn generate_grid( + rng: &mut SmallRng, + solve_controller: &SolveController, +) -> (Grid, i32, SolveStatistics) { let mut grid = generate_completed_grid(rng); let mut num_hints = 81; @@ -132,8 +158,8 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) -> cell_clone.delete_value(); - - let (status, statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller); + let (status, statistics) = + evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller); match status { SolveStatus::Complete(uniqueness) => { let uniqueness = uniqueness.unwrap(); @@ -142,31 +168,34 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) -> num_hints = num_hints - 1; grid = grid_clone; } - Uniqueness::NotUnique => continue // We can't remove this cell; continue onto the next one (note that grid hasn't been modified because of solve_controller) + Uniqueness::NotUnique => continue, // We can't remove this cell; continue onto the next one (note that grid hasn't been modified because of solve_controller) } } - SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED"), - SolveStatus::Invalid => panic!("Removing constraints should not have set the # of solutions to zero") + SolveStatus::Unfinished => { + panic!("evaluate_grid_with_solve_controller should never return UNFINISHED") + } + SolveStatus::Invalid => { + panic!("Removing constraints should not have set the # of solutions to zero") + } } statistics_option = Some(statistics); } return (grid, num_hints, statistics_option.unwrap()); - } // We generate a completed grid with no mind for difficulty; afterward generate_puzzle will take out as many fields as it can with regards to the difficulty fn generate_completed_grid(rng: &mut SmallRng) -> Grid { - let solve_controller = SolveController{ + let solve_controller = SolveController { determine_uniqueness: true, search_singles: true, search_hidden_singles: true, find_possibility_groups: true, search_useful_constraint: true, - make_guesses: true + make_guesses: true, }; - let mut grid : Grid = loop { + let mut grid: Grid = loop { // First step; randomly assign 8 different digits to different empty cells and see if there's a possible solution // We have to ensure that 8 of the digits appear at least once, otherwise the solution can't be unique because you could interchange the two missing digits throughout the puzzle // We do this in a loop so that if we are really unlucky and our guesses stop there from being any solution, we can easily re-run it @@ -199,24 +228,25 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid { }; // Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution - grid = - 'outer: loop { - let cell = grid.get_random_empty_cell(rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic - let cell = &*cell; - let mut cell_possibilities = cell.get_value_possibilities().expect("An empty cell has no possibilities"); + grid = 'outer: loop { + let cell = grid.get_random_empty_cell(rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic + let cell = &*cell; + let mut cell_possibilities = cell + .get_value_possibilities() + .expect("An empty cell has no possibilities"); - // Let's scramble the order - cell_possibilities.shuffle(rng); + // Let's scramble the order + cell_possibilities.shuffle(rng); - for (_index, digit) in cell_possibilities.iter().enumerate() { + for (_index, digit) in cell_possibilities.iter().enumerate() { + let mut grid_clone = grid.clone(); + let cell = &*grid_clone.get(cell.x, cell.y).unwrap(); - let mut grid_clone = grid.clone(); - let cell = &*grid_clone.get(cell.x, cell.y).unwrap(); + cell.set(*digit); - cell.set(*digit); - - let (status, _statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller); - match status { + let (status, _statistics) = + evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller); + match status { SolveStatus::Complete(uniqueness) => { let uniqueness = uniqueness.unwrap(); match uniqueness { @@ -230,27 +260,29 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid { SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if making guesses"), SolveStatus::Invalid => continue // Try another guess } + } - }; - - // If we reach this point in the loop, then none of the possibilities for cell provided any solution - // Which means something serious happened before in the solving process - reason for panic - eprint!("No valid hints were found for puzzle\n{} at cell ({}, {})", grid, cell.x, cell.y); - panic!("Unable to continue as puzzle is invalid"); - - }; + // If we reach this point in the loop, then none of the possibilities for cell provided any solution + // Which means something serious happened before in the solving process - reason for panic + eprint!( + "No valid hints were found for puzzle\n{} at cell ({}, {})", + grid, cell.x, cell.y + ); + panic!("Unable to continue as puzzle is invalid"); + }; crate::solver::solve_grid(&mut grid); return grid; } - #[cfg(test)] mod tests { - use crate::grid::*; - use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics}; use crate::generator::generate_grid; + use crate::grid::*; + use crate::solver::{ + solve_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness, + }; use rand::prelude::SmallRng; use rand::SeedableRng; @@ -292,36 +324,40 @@ mod tests { grid.get(8, 2).unwrap().set(6); - let status = solve_grid_with_solve_controller(&mut grid, &SolveController{ - determine_uniqueness: true, - search_singles: true, - search_hidden_singles: true, - find_possibility_groups: true, - search_useful_constraint: true, - make_guesses: true - }, &mut SolveStatistics::new()); + let status = solve_grid_with_solve_controller( + &mut grid, + &SolveController { + determine_uniqueness: true, + search_singles: true, + search_hidden_singles: true, + find_possibility_groups: true, + search_useful_constraint: true, + make_guesses: true, + }, + &mut SolveStatistics::new(), + ); assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique))); - } // There was a bug where even though mutate_grid was set to false, the end result was still solved #[test] - fn ensure_grid_not_complete(){ - let solve_controller = SolveController{ + fn ensure_grid_not_complete() { + let solve_controller = SolveController { determine_uniqueness: true, search_singles: true, search_hidden_singles: true, find_possibility_groups: true, search_useful_constraint: true, - make_guesses: true + make_guesses: true, }; // Note that the puzzle itself doesn't matter - let (grid, _num_hints, _statistics) = generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller); + let (grid, _num_hints, _statistics) = + generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller); let mut observed_empty_cell = false; - 'outer : for x in 0..9 { + 'outer: for x in 0..9 { for y in 0..9 { let cell = grid.get(x, y).unwrap(); let value = cell.get_value_copy(); @@ -337,6 +373,5 @@ mod tests { } assert!(observed_empty_cell); - } -} \ No newline at end of file +} diff --git a/src/grid.rs b/src/grid.rs index 651388c..44296d4 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,13 +1,13 @@ -use std::rc::{Rc, Weak}; -use std::cell::{RefCell}; +use std::cell::RefCell; use std::fmt::Formatter; +use std::rc::{Rc, Weak}; pub static mut DEBUG: bool = false; #[derive(Clone, Debug, Eq, PartialEq)] pub enum CellValue { Fixed(u8), - Unknown(Vec) + Unknown(Vec), } /// A representation of a single cell in a Sudoku grid. Don't make this directly; make a Grid. @@ -42,7 +42,7 @@ impl Cell { /// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![2,3,4,5,6,7,8,9])); /// /// ``` - pub fn set(&self, digit: u8){ + pub fn set(&self, digit: u8) { unsafe { if DEBUG { println!("Cell {}, {} was set with digit {}", self.x, self.y, digit); @@ -76,12 +76,12 @@ impl Cell { /// 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){ + pub fn set_value(&self, value: CellValue) { match value { CellValue::Fixed(digit) => { self.set(digit); return; - }, + } CellValue::Unknown(_) => { self.set_value_exact(value); } // continue on @@ -108,10 +108,13 @@ impl Cell { /// 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){ + pub fn set_value_exact(&self, value: CellValue) { unsafe { if DEBUG { - println!("Cell {}, {} was set with CellValue exact {:?}", self.x, self.y, value); + println!( + "Cell {}, {} was set with CellValue exact {:?}", + self.x, self.y, value + ); } } @@ -124,13 +127,13 @@ impl Cell { let value = &*self.value.borrow(); match value { CellValue::Fixed(_) => None, - CellValue::Unknown(x) => Some(x.clone()) + CellValue::Unknown(x) => Some(x.clone()), } } // 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){ + fn mark_updates(&self) { { let row = &*self.row.upgrade().unwrap(); let row = &*row.borrow(); @@ -149,12 +152,12 @@ impl Cell { } // Go through and remove digit from the Section's Cells' possibilities - fn process_possibilities(line: &Section, digit: u8){ + fn process_possibilities(line: &Section, digit: u8) { for (_index, cell) in line.vec.iter().enumerate() { let cell = &**cell; // Find the new CellValue to set; may be None if the cell was already fixed or had no possibilities remaining - let new_value_option : Option = { + let new_value_option: Option = { let value = &*cell.value.borrow(); match value { @@ -162,7 +165,9 @@ impl Cell { let mut new_possibilities = possibilities.clone(); match new_possibilities.binary_search(&digit) { - Ok(index_remove) => {new_possibilities.remove(index_remove);}, + Ok(index_remove) => { + new_possibilities.remove(index_remove); + } _ => {} }; @@ -176,18 +181,17 @@ impl Cell { } else { Some(CellValue::UNKNOWN(new_possibilities)) }*/ - }, - CellValue::Fixed(_) => {None} + } + CellValue::Fixed(_) => None, } }; match new_value_option { Some(new_value) => { cell.set_value(new_value); - }, + } None => {} } - } } } @@ -200,23 +204,23 @@ pub struct Section { pub vec: Vec>, pub do_update: RefCell, pub index: usize, - pub section_type: SectionType + pub section_type: SectionType, } #[derive(Debug)] pub enum SectionType { Row, Column, - Square + Square, } impl Section { - fn push(&mut self, x: Rc){ + 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>{ + pub fn get(&self, index: usize) -> Option<&Rc> { self.vec.get(index) } @@ -225,7 +229,7 @@ impl Section { vec: Vec::new(), do_update: RefCell::new(false), index, - section_type: line_type + section_type: line_type, } } @@ -250,7 +254,6 @@ pub struct Grid { 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(); @@ -262,17 +265,17 @@ impl Grid { } for row_index in 0..9 { - let row_rc = unsafe { - rows.get_unchecked(row_index) - }; + let row_rc = unsafe { rows.get_unchecked(row_index) }; let row_ref = &mut *row_rc.borrow_mut(); for column_index in 0..9 { let section_index = (row_index / 3) * 3 + column_index / 3; let (column_rc, section_rc) = unsafe { - (columns.get_unchecked_mut(column_index), - sections.get_unchecked_mut(section_index)) + ( + columns.get_unchecked_mut(column_index), + sections.get_unchecked_mut(section_index), + ) }; let column_weak = Rc::downgrade(column_rc); @@ -289,7 +292,7 @@ impl Grid { value: RefCell::new(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])), row: row_weak, column: column_weak, - section: section_weak + section: section_weak, }; let ref1 = Rc::new(cell); @@ -302,7 +305,11 @@ impl Grid { } } - return Grid { rows, columns, sections }; + return Grid { + rows, + columns, + sections, + }; } /// Returns the `Cell` (in an `Rc`) at the specified coordinates. @@ -311,34 +318,33 @@ impl Grid { /// /// 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 None + None => return None, }; let row = &*(&**row).borrow(); let cell = match row.get(c) { Some(x) => x, - None => return None + None => return None, }; return Some(Rc::clone(cell)); } - fn process_unknown(x: &Vec, digit: u8, row: &mut String){ + fn process_unknown(x: &Vec, digit: u8, row: &mut String) { if x.contains(&digit) { row.push('*'); - } else{ + } else { 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; + pub fn find_smallest_cell(&self) -> Option> { + let mut smallest_cell: Option> = None; let mut smallest_size = usize::MAX; for x in 0..9 { @@ -349,11 +355,11 @@ impl Grid { match cell_value { CellValue::Unknown(possibilities) => { - if (possibilities.len() < smallest_size) && (possibilities.len() > 0){ + if (possibilities.len() < smallest_size) && (possibilities.len() > 0) { smallest_size = possibilities.len(); smallest_cell = Some(cell_rc); } - }, + } _ => {} } } @@ -397,24 +403,23 @@ impl Clone for Grid { impl std::fmt::Display for Grid { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for r in 0..9 { - // Each row corresponds to 3 rows since we leave room for guesses let mut row1 = String::new(); let mut row2 = String::new(); let mut row3 = String::new(); for c in 0..9 { - let cell = &*self.get(r, c).unwrap(); let value = &*cell.value.borrow(); - match value { CellValue::Fixed(x) => { row1.push_str(" "); - row2.push(' '); row2.push_str(&x.to_string()); row2.push(' '); + row2.push(' '); + row2.push_str(&x.to_string()); + row2.push(' '); row3.push_str(" "); - }, + } CellValue::Unknown(x) => { Grid::process_unknown(&x, 1, &mut row1); Grid::process_unknown(&x, 2, &mut row1); @@ -430,17 +435,15 @@ impl std::fmt::Display for Grid { } }; - if (c % 3 == 2) && (c < 8){ + if (c % 3 == 2) && (c < 8) { row1.push('\u{2503}'); row2.push('\u{2503}'); row3.push('\u{2503}'); - } else if c < 8{ + } else if c < 8 { row1.push('┆'); row2.push('┆'); row3.push('┆'); } - - } write!(f, "{}", row1)?; @@ -452,11 +455,11 @@ impl std::fmt::Display for Grid { if (r % 3 == 2) && (r < 8) { write!(f, "━━━┿━━━┿━━━╋━━━┿━━━┿━━━╋━━━┿━━━┿━━━\n")?; - } else if r < 8{ + } else if r < 8 { write!(f, "┄┄┄┼┄┄┄┼┄┄┄╂┄┄┄┼┄┄┄┼┄┄┄╂┄┄┄┼┄┄┄┼┄┄┄\n")?; } } return Result::Ok(()); } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index c76ea25..87b7384 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub mod grid; -pub mod solver; pub mod generator; -pub mod pdf; \ No newline at end of file +pub mod grid; +pub mod pdf; +pub mod solver; diff --git a/src/pdf.rs b/src/pdf.rs index 1d742c7..35b72b2 100644 --- a/src/pdf.rs +++ b/src/pdf.rs @@ -1,15 +1,15 @@ +use crate::grid::{CellValue, Grid}; use printpdf::*; use std::fs::File; use std::io::BufWriter; -use crate::grid::{Grid, CellValue}; -const BOTTOM_LEFT_X : f64 = 10.0; -const BOTTOM_LEFT_Y : f64 = 279.0 - 200.0 - 10.0; -const GRID_DIMENSION : f64 = 190.0; +const BOTTOM_LEFT_X: f64 = 10.0; +const BOTTOM_LEFT_Y: f64 = 279.0 - 200.0 - 10.0; +const GRID_DIMENSION: f64 = 190.0; -const A4 : (Mm, Mm) = (Mm(215.0), Mm(279.0)); +const A4: (Mm, Mm) = (Mm(215.0), Mm(279.0)); -pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box>{ +pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box> { let (doc, page1, layer1) = PdfDocument::new("Sudoku Puzzle", A4.0, A4.1, "Layer 1"); let layer = doc.get_page(page1).get_layer(layer1); @@ -28,7 +28,6 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box Result<(), Box { let text = digit.to_string(); layer.use_text(text, font_size, x, y, &font); - }, + } _ => {} } } @@ -50,11 +49,9 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box), Unfinished, - Invalid + Invalid, } /// See `SolveController` for a description of the solving strategies. -enum SolveAction{ +enum SolveAction { Single, HiddenSingle, PossibilityGroup, UsefulConstraints, - Guess + Guess, } impl SolveStatus { - - fn increment(self, additional_status : SolveStatus) -> SolveStatus { + fn increment(self, additional_status: SolveStatus) -> SolveStatus { match self { SolveStatus::Complete(uniqueness_option) => { if uniqueness_option.is_none() { @@ -37,19 +35,24 @@ impl SolveStatus { match uniqueness_option.unwrap() { Uniqueness::NotUnique => SolveStatus::Complete(Some(Uniqueness::NotUnique)), Uniqueness::Unique => match additional_status { - SolveStatus::Complete(_) => SolveStatus::Complete(Some(Uniqueness::NotUnique)), - SolveStatus::Unfinished => SolveStatus::Complete(Some(Uniqueness::Unique)), - SolveStatus::Invalid => SolveStatus::Complete(Some(Uniqueness::Unique)) - } + SolveStatus::Complete(_) => { + SolveStatus::Complete(Some(Uniqueness::NotUnique)) + } + SolveStatus::Unfinished => { + SolveStatus::Complete(Some(Uniqueness::Unique)) + } + SolveStatus::Invalid => SolveStatus::Complete(Some(Uniqueness::Unique)), + }, } } - } SolveStatus::Unfinished => match additional_status { SolveStatus::Invalid => SolveStatus::Unfinished, - _ => additional_status + _ => additional_status, }, - SolveStatus::Invalid => panic!("increment() shouldn't be called on SolveStatus::Invalid") + SolveStatus::Invalid => { + panic!("increment() shouldn't be called on SolveStatus::Invalid") + } } } } @@ -117,47 +120,42 @@ pub struct SolveStatistics { pub hidden_singles: u32, pub possibility_groups: u32, pub useful_constraints: u32, - pub guesses: u32 + pub guesses: u32, } impl SolveStatistics { - /// Create a new SolveStatistics with `0` counts set for all the fields. pub fn new() -> SolveStatistics { - SolveStatistics{ + SolveStatistics { singles: 0, hidden_singles: 0, possibility_groups: 0, useful_constraints: 0, - guesses: 0 + guesses: 0, } } fn increment(&mut self, action: &SolveAction) { match action { - SolveAction::Single => {self.singles = self.singles + 1} - SolveAction::HiddenSingle => {self.hidden_singles = self.hidden_singles + 1} - SolveAction::PossibilityGroup => {self.possibility_groups = self.possibility_groups + 1} - SolveAction::UsefulConstraints => {self.useful_constraints = self.useful_constraints + 1} - SolveAction::Guess => {self.guesses = self.guesses + 1} + SolveAction::Single => self.singles = self.singles + 1, + SolveAction::HiddenSingle => self.hidden_singles = self.hidden_singles + 1, + SolveAction::PossibilityGroup => self.possibility_groups = self.possibility_groups + 1, + SolveAction::UsefulConstraints => self.useful_constraints = self.useful_constraints + 1, + SolveAction::Guess => self.guesses = self.guesses + 1, } } } - - - - // Code for identify_and_process_possibility_groups (it uses it's own structs) mod process_possibility_groups { - use crate::grid::{Section, CellValue}; + use crate::grid::{CellValue, Section}; use std::collections::HashSet; use std::rc::Rc; - struct FauxCell{ + struct FauxCell { index: usize, possibilities: HashSet, - in_group: bool + in_group: bool, } impl FauxCell { @@ -165,15 +163,16 @@ mod process_possibility_groups { self.possibilities.len() } - fn remove(&mut self, to_remove: &HashSet){ - to_remove.iter().for_each(|digit| {self.possibilities.remove(digit);}); + fn remove(&mut self, to_remove: &HashSet) { + to_remove.iter().for_each(|digit| { + self.possibilities.remove(digit); + }); } } - struct FauxLine (Vec); + struct FauxLine(Vec); impl FauxLine { - fn num_in_group(&self) -> usize { self.0.iter().filter(|faux_cell| faux_cell.in_group).count() } @@ -185,27 +184,29 @@ 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: &Section) -> bool{ + pub fn identify_and_process_possibility_groups(line: &Section) -> bool { unsafe { if super::DEBUG { - println!("Looking for possibility groups on line {:?} {}", line.section_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: &Section, cells_of_interest: Vec) -> bool{ - + fn bisect_possibility_groups(line: &Section, cells_of_interest: Vec) -> bool { /* - Algorithm - - Setup - Let count = 0 - 1. Choose cell with least number of possibilities. Add to in-group. - 2. Add to count the number of possibilities in that cell - 3. Remove the possibilities of that cell from all other out-group cells. - 4. If the number of cells in group == count, finish. - 5. Goto 1 - */ + Algorithm - + Setup - Let count = 0 + 1. Choose cell with least number of possibilities. Add to in-group. + 2. Add to count the number of possibilities in that cell + 3. Remove the possibilities of that cell from all other out-group cells. + 4. If the number of cells in group == count, finish. + 5. Goto 1 + */ // For later recursive calls; put here because of scope reasons let mut in_group_indices = Vec::new(); let mut out_group_indices = Vec::new(); @@ -235,15 +236,15 @@ mod process_possibility_groups { set.insert(digit.clone()); } set - }, - CellValue::Fixed(_) => { continue } + } + CellValue::Fixed(_) => continue, } }; let faux_cell = FauxCell { index: i, possibilities: faux_possibilities, - in_group: false + in_group: false, }; faux_line.0.push(faux_cell); @@ -265,7 +266,12 @@ mod process_possibility_groups { let mut smallest_cell: Option<&mut FauxCell> = None; let mut smallest_size = usize::MAX; - for (_index, cell) in faux_line.0.iter_mut().filter(|faux_cell| !faux_cell.in_group).enumerate() { + for (_index, cell) in faux_line + .0 + .iter_mut() + .filter(|faux_cell| !faux_cell.in_group) + .enumerate() + { if cell.len() < smallest_size { smallest_size = cell.len(); smallest_cell = Some(cell); @@ -278,11 +284,15 @@ mod process_possibility_groups { // Step 2 count = count + smallest_size; - let possibilities_to_remove = smallest_cell.possibilities.clone(); // Necessary because of mutable borrow rules // Step 3 - for (_index, cell) in faux_line.0.iter_mut().filter(|faux_cell| !faux_cell.in_group).enumerate() { + for (_index, cell) in faux_line + .0 + .iter_mut() + .filter(|faux_cell| !faux_cell.in_group) + .enumerate() + { cell.remove(&possibilities_to_remove); } @@ -293,7 +303,8 @@ mod process_possibility_groups { } // Now we have to see if this was worth it - if faux_line.num_out_group() > 0 { // Worth it + if faux_line.num_out_group() > 0 { + // Worth it // We now have two distinct groups and can separate their possibilities let mut in_group_possibilities = HashSet::new(); let mut out_group_possibilities = HashSet::new(); @@ -301,9 +312,13 @@ mod process_possibility_groups { // Collect the possibilities for each group for (_index, cell) in faux_line.0.iter().enumerate() { if cell.in_group { - cell.possibilities.iter().for_each(|digit| {in_group_possibilities.insert(digit.clone());}); + cell.possibilities.iter().for_each(|digit| { + in_group_possibilities.insert(digit.clone()); + }); } else { - cell.possibilities.iter().for_each(|digit| {out_group_possibilities.insert(digit.clone());}); + cell.possibilities.iter().for_each(|digit| { + out_group_possibilities.insert(digit.clone()); + }); } } @@ -314,26 +329,29 @@ mod process_possibility_groups { let value = &*real_cell.value.borrow(); match value { CellValue::Unknown(possibilities) => possibilities.clone(), - CellValue::Fixed(_) => {panic!("Faux_cell shouldn't have linked to fixed cell")} + CellValue::Fixed(_) => { + panic!("Faux_cell shouldn't have linked to fixed cell") + } } }; let starting_possibility_size = possibilities.len(); let possibilities_to_remove = match faux_cell.in_group { true => &out_group_possibilities, - false => &in_group_possibilities + false => &in_group_possibilities, }; for (_i, possibility) in possibilities_to_remove.iter().enumerate() { match possibilities.binary_search(possibility) { Ok(x) => { possibilities.remove(x); - }, + } Err(_) => {} }; } - if possibilities.len() < starting_possibility_size { // We have a change to make + if possibilities.len() < starting_possibility_size { + // We have a change to make made_change = true; let new_value = { if possibilities.len() == 1 { @@ -371,24 +389,27 @@ 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: &Section) -> bool{ +fn search_single_possibility(line: &Section) -> bool { unsafe { if DEBUG { - println!("search_single_possibility on line {:?} {}", line.section_type, line.index); + println!( + "search_single_possibility on line {:?} {}", + line.section_type, line.index + ); } } let mut made_change = false; - for (_index, cell) in line.vec.iter().enumerate(){ - match cell.get_value_possibilities(){ + for (_index, cell) in line.vec.iter().enumerate() { + match cell.get_value_possibilities() { Some(x) => { if x.len() == 1 { let new_value = CellValue::Fixed(x.first().unwrap().clone()); cell.set_value(new_value); made_change = true; } - }, + } None => {} } } @@ -397,37 +418,47 @@ fn search_single_possibility(line: &Section) -> 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: &Section) -> bool{ +fn search_hidden_single(line: &Section) -> bool { enum Count { None, One(Rc), - Many + Many, }; impl Count { - fn increment(&self, cell: Rc) -> Count{ + fn increment(&self, cell: Rc) -> Count { match self { - Count::None => {Count::One(cell)} - Count::One(_) => {Count::Many} - Count::Many => {Count::Many} + Count::None => Count::One(cell), + Count::One(_) => Count::Many, + Count::Many => Count::Many, } } } let mut made_change = false; - let mut counts = [Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None, Count::None]; + let mut counts = [ + Count::None, + Count::None, + Count::None, + Count::None, + Count::None, + Count::None, + Count::None, + Count::None, + Count::None, + ]; for (_index, cell) in line.vec.iter().enumerate() { let value = &*cell.value.borrow(); match value { CellValue::Unknown(possibilities) => { for digit in 1..10 { - if possibilities.contains(&(digit as u8)){ - counts[digit-1] = counts[digit-1].increment(Rc::clone(cell)); + if possibilities.contains(&(digit as u8)) { + counts[digit - 1] = counts[digit - 1].increment(Rc::clone(cell)); } } - }, + } CellValue::Fixed(_) => {} // do nothing } } @@ -437,7 +468,7 @@ fn search_hidden_single(line: &Section) -> bool{ Count::One(cell) => { cell.set((digit + 1) as u8); made_change = true; - }, + } _ => {} } } @@ -445,49 +476,61 @@ fn search_hidden_single(line: &Section) -> bool{ return made_change; } -mod search_useful_constraint{ - use crate::grid::{Grid, Section, SectionType, CellValue}; - use std::rc::{Rc, Weak}; +mod search_useful_constraint { + use crate::grid::{CellValue, Grid, Section, SectionType}; use std::cell::RefCell; + use std::rc::{Rc, Weak}; enum PossibilityLines { Unique(usize), Invalid, - None + None, } impl PossibilityLines { fn is_invalid(&self) -> bool { match &self { PossibilityLines::Invalid => true, - _ => false + _ => false, } } } // If all the cells for a particular possibility share a same other Line, they can remove that possibility from other cells in the main line. -// 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: &Section) -> bool{ + // 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: &Section) -> bool { unsafe { if super::DEBUG { - println!("Searching for a useful constraint on line {:?} {}", line.section_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.section_type { - SectionType::Row => {(false, false, true)}, - SectionType::Column => {(false, false, true)}, - SectionType::Square => {(true, true, false)}, + SectionType::Row => (false, false, true), + SectionType::Column => (false, false, true), + SectionType::Square => (true, true, false), }; for possibility in 0..9 { - let mut rows = match check_row {true => PossibilityLines::None, false => PossibilityLines::Invalid }; - let mut columns = match check_column {true => PossibilityLines::None, false => PossibilityLines::Invalid }; - let mut sections = match check_section {true => PossibilityLines::None, false => PossibilityLines::Invalid }; + let mut rows = match check_row { + true => PossibilityLines::None, + false => PossibilityLines::Invalid, + }; + let mut columns = match check_column { + true => PossibilityLines::None, + false => PossibilityLines::Invalid, + }; + let mut sections = match check_section { + true => PossibilityLines::None, + false => PossibilityLines::Invalid, + }; for cell_id in 0..9 { let cell_ref = line.get(cell_id).unwrap(); @@ -496,7 +539,8 @@ mod search_useful_constraint{ let value = &*cell_ref.value.borrow(); match value { - CellValue::Fixed(x) => { // We can deduce this possibility won't occur elsewhere in our row, so leave for-loop + CellValue::Fixed(x) => { + // We can deduce this possibility won't occur elsewhere in our row, so leave for-loop if possibility.eq(x) { rows = process_possibility_line(rows, &cell_ref.row); columns = process_possibility_line(columns, &cell_ref.column); @@ -521,33 +565,52 @@ mod search_useful_constraint{ // Check each line and see if we can determine anything match rows { PossibilityLines::Unique(index) => { - made_change = made_change | - remove_possibilities_line(grid.rows.get(index).unwrap(), possibility, &line.section_type, line.index); - }, + made_change = made_change + | 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.section_type, line.index); - }, + made_change = made_change + | 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.section_type, line.index); - }, + made_change = made_change + | remove_possibilities_line( + grid.sections.get(index).unwrap(), + possibility, + &line.section_type, + line.index, + ); + } _ => {} } } return made_change; - } // 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: &SectionType, 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; @@ -559,7 +622,7 @@ mod search_useful_constraint{ let parent_line = match initial_line_type { SectionType::Row => &cell.row, SectionType::Column => &cell.column, - SectionType::Square => &cell.section + SectionType::Square => &cell.section, }; let parent_line = &*parent_line.upgrade().unwrap(); let parent_line = &*parent_line.borrow(); @@ -568,44 +631,52 @@ mod search_useful_constraint{ continue; } - let new_possibilities = match possibilities.binary_search(&digit_to_remove) { + let new_possibilities = match possibilities.binary_search(&digit_to_remove) + { Ok(x) => { let mut new_possibilities = possibilities.clone(); new_possibilities.remove(x); new_possibilities - }, - Err(_) => { continue; } + } + Err(_) => { + continue; + } }; let new_value; if new_possibilities.len() == 1 { - new_value = CellValue::Fixed(new_possibilities.first().unwrap().clone()); + new_value = + CellValue::Fixed(new_possibilities.first().unwrap().clone()); } else { new_value = CellValue::Unknown(new_possibilities); } new_value - }, - _ => { continue; } + } + _ => { + continue; + } } }; cell.set_value(new_value); made_change = true; - } return made_change; } // 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(); match possibility_line { - PossibilityLines::None => {PossibilityLines::Unique(line.index)}, - PossibilityLines::Invalid => {possibility_line}, + PossibilityLines::None => PossibilityLines::Unique(line.index), + PossibilityLines::Invalid => possibility_line, PossibilityLines::Unique(x) => { if line.index.eq(&x) { possibility_line @@ -615,11 +686,14 @@ mod search_useful_constraint{ } } } - } - -fn solve_line(grid: &Grid, line: &Section, 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.section_type, line.index); @@ -631,7 +705,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s if solve_controller.search_singles() { unsafe { if DEBUG { - println!("Searching for singles on line {:?} of {}\n{}", line.section_type, line.index, grid); + println!( + "Searching for singles on line {:?} of {}\n{}", + line.section_type, line.index, grid + ); } } if search_single_possibility(line) { @@ -642,7 +719,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s if solve_controller.search_hidden_singles() { unsafe { if DEBUG { - println!("Searching for hidden singles on line {:?} of {}\n{}", line.section_type, line.index, grid); + println!( + "Searching for hidden singles on line {:?} of {}\n{}", + line.section_type, line.index, grid + ); } } if search_hidden_single(line) { @@ -653,7 +733,10 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s if solve_controller.find_possibility_groups() { unsafe { if DEBUG { - println!("Searching for possibility groups on line {:?} of {}\n{}", line.section_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) { @@ -664,14 +747,16 @@ fn solve_line(grid: &Grid, line: &Section, solve_controller: &SolveController, s if solve_controller.search_useful_constraint() { unsafe { if DEBUG { - println!("Searching for useful constraints on line {:?} of {}\n{}", line.section_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) { solve_statistics.increment(&SolveAction::UsefulConstraints); } } - } /// Solves (and modifies) the input `Grid`. Returns a `SolveStatus` and `SolveStatistics`, and @@ -685,17 +770,22 @@ pub fn solve_grid(grid: &mut Grid) -> (SolveStatus, SolveStatistics) { search_hidden_singles: true, find_possibility_groups: true, search_useful_constraint: true, - make_guesses: true + make_guesses: true, }; let mut solve_statistics = SolveStatistics::new(); - let solve_status = solve_grid_with_solve_controller(grid, &solve_controller, &mut solve_statistics); + let solve_status = + solve_grid_with_solve_controller(grid, &solve_controller, &mut solve_statistics); 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{ +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 // solve_grid_guess runs through all the possibilities for the smallest cell, trying to solve them @@ -711,8 +801,8 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv } else { SolveStatus::Complete(Some(Uniqueness::NotUnique)) // solve_grid_no_guess couldn't finish and we can't make guesses, so it's 'not unique' in the sense that we need more guesses } - }, - _ => {status} + } + _ => status, }; return status; @@ -721,17 +811,24 @@ pub fn solve_grid_with_solve_controller(grid: &mut Grid, solve_controller: &Solv /// 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){ +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(); - let solve_status = solve_grid_with_solve_controller(&mut mut_grid, solve_controller, &mut solve_statistics); + let solve_status = + solve_grid_with_solve_controller(&mut mut_grid, solve_controller, &mut solve_statistics); return (solve_status, solve_statistics); } -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; for (_index, line_ref) in grid.rows.iter().enumerate() { @@ -759,7 +856,8 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv } } - if !ran_something{ // No lines have changed since we last analyzed them + if !ran_something { + // No lines have changed since we last analyzed them return SolveStatus::Unfinished; } @@ -776,9 +874,8 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv appears_complete = false; if possibilities.len() == 0 { return SolveStatus::Invalid; - } - }, + } CellValue::Fixed(_) => {} } } @@ -790,16 +887,19 @@ fn solve_grid_no_guess(grid: &mut Grid, solve_controller: &SolveController, solv return SolveStatus::Complete(Some(Uniqueness::Unique)); } } - } -fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_statistics: &mut SolveStatistics) -> SolveStatus{ +fn solve_grid_guess( + grid: &mut Grid, + solve_controller: &SolveController, + solve_statistics: &mut SolveStatistics, +) -> SolveStatus { solve_statistics.increment(&SolveAction::Guess); let smallest_cell = grid.find_smallest_cell(); let smallest_cell = match smallest_cell { Some(cell) => cell, - None => return SolveStatus::Invalid + None => return SolveStatus::Invalid, }; let possibilities = smallest_cell.get_value_possibilities().unwrap(); @@ -808,16 +908,19 @@ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_s let mut grid_solution = None; for (_index, &digit) in possibilities.iter().enumerate() { - let mut grid_copy = grid.clone(); - grid_copy.get(smallest_cell.x, smallest_cell.y).unwrap().set(digit); - let status = solve_grid_with_solve_controller(&mut grid_copy, solve_controller, solve_statistics); + grid_copy + .get(smallest_cell.x, smallest_cell.y) + .unwrap() + .set(digit); + let status = + solve_grid_with_solve_controller(&mut grid_copy, solve_controller, solve_statistics); // Keep a copy of grid_copy in case we later mutate grid with it match status { SolveStatus::Complete(_) => { grid_solution = Some(grid_copy); - }, + } _ => {} } @@ -830,34 +933,37 @@ fn solve_grid_guess(grid: &mut Grid, solve_controller: &SolveController, solve_s break; // no point in continuing } - let uniqueness = uniqueness.expect("We're looking for uniqueness and yet an earlier function didn't make a claim"); + let uniqueness = uniqueness.expect( + "We're looking for uniqueness and yet an earlier function didn't make a claim", + ); match uniqueness { - Uniqueness::Unique => {continue;} // gotta keep on checking + Uniqueness::Unique => { + continue; + } // gotta keep on checking Uniqueness::NotUnique => { break; // We can stop looking as we already found at least two solutions } } } - SolveStatus::Unfinished => {continue} // Keep looking for a solution - SolveStatus::Invalid => panic!("current_status should not be INVALID at this point") + SolveStatus::Unfinished => continue, // Keep looking for a solution + SolveStatus::Invalid => panic!("current_status should not be INVALID at this point"), } - - } // We've finished the for-loop match current_status { SolveStatus::Complete(_) => { - grid.clone_from(&grid_solution.expect("grid_solution should have value if we found a solution")); - }, + grid.clone_from( + &grid_solution.expect("grid_solution should have value if we found a solution"), + ); + } SolveStatus::Unfinished => { current_status = SolveStatus::Invalid; // We can now say Invalid - }, + } SolveStatus::Invalid => {} } return current_status; - } #[cfg(test)] @@ -866,72 +972,87 @@ mod tests { use crate::solver::*; #[test] - fn test_search_single_possibility(){ + fn test_search_single_possibility() { let grid = Grid::new(); for i in 0..8 { - grid.get(0, i).unwrap().set(i as u8 +1); + grid.get(0, i).unwrap().set(i as u8 + 1); } - assert_eq!(CellValue::Unknown(vec![9]), grid.get(0, 8).unwrap().get_value_copy()); + assert_eq!( + CellValue::Unknown(vec![9]), + grid.get(0, 8).unwrap().get_value_copy() + ); let line = grid.rows.first().unwrap(); let line = &*(**line).borrow(); search_single_possibility(line); - assert_eq!(CellValue::Fixed(9), grid.get(0, 8).unwrap().get_value_copy()); + assert_eq!( + CellValue::Fixed(9), + grid.get(0, 8).unwrap().get_value_copy() + ); } #[test] - fn test_identify_and_process_possibility_groups(){ + fn test_identify_and_process_possibility_groups() { let grid = Grid::new(); // Fill up the first row with values, except for the first section for i in 3..6 { - grid.get(0, i).unwrap().set(i as u8 +1); + grid.get(0, i).unwrap().set(i as u8 + 1); } grid.get(1, 6).unwrap().set(1); grid.get(1, 7).unwrap().set(2); grid.get(1, 8).unwrap().set(3); - assert_eq!(CellValue::Unknown(vec![1, 2, 3, 7, 8, 9]), grid.get(0, 0).unwrap().get_value_copy()); + assert_eq!( + CellValue::Unknown(vec![1, 2, 3, 7, 8, 9]), + grid.get(0, 0).unwrap().get_value_copy() + ); let line = grid.rows.first().unwrap(); let line = &*(**line).borrow(); process_possibility_groups::identify_and_process_possibility_groups(line); - assert_eq!(CellValue::Unknown(vec![1, 2, 3]), grid.get(0, 0).unwrap().get_value_copy()); + assert_eq!( + CellValue::Unknown(vec![1, 2, 3]), + grid.get(0, 0).unwrap().get_value_copy() + ); } #[test] - fn test_search_useful_constraint_1(){ + fn test_search_useful_constraint_1() { let grid = Grid::new(); // Fill up the first row with values, except for the first section for i in 3..6 { - grid.get(0, i).unwrap().set(i as u8 +1); + grid.get(0, i).unwrap().set(i as u8 + 1); } grid.get(1, 6).unwrap().set(1); grid.get(1, 7).unwrap().set(2); grid.get(1, 8).unwrap().set(3); - - - assert_eq!(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy()); + assert_eq!( + CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), + grid.get(2, 0).unwrap().get_value_copy() + ); let line = grid.rows.first().unwrap(); let line = &*(**line).borrow(); search_useful_constraint::search_useful_constraint(&grid, line); - assert_eq!(CellValue::Unknown(vec![4, 5, 6, 7, 8, 9]), grid.get(2, 0).unwrap().get_value_copy()); + assert_eq!( + CellValue::Unknown(vec![4, 5, 6, 7, 8, 9]), + grid.get(2, 0).unwrap().get_value_copy() + ); } - #[test] - fn test_search_useful_constraint_2(){ + fn test_search_useful_constraint_2() { let grid = Grid::new(); // These values come from a specific bug example where a constraint was incorrectly identified @@ -942,15 +1063,29 @@ mod tests { grid.get(6, 1).unwrap().set(8); grid.get(8, 2).unwrap().set(7); - grid.get(0, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9])); - grid.get(1, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9])); - grid.get(2, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2])); - grid.get(4, 1).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 7])); - grid.get(7, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 9])); - grid.get(8, 1).unwrap().set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9])); + grid.get(0, 1) + .unwrap() + .set_value(CellValue::Unknown(vec![1, 3, 4, 7, 9])); + grid.get(1, 1) + .unwrap() + .set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9])); + grid.get(2, 1) + .unwrap() + .set_value(CellValue::Unknown(vec![1, 2])); + grid.get(4, 1) + .unwrap() + .set_value(CellValue::Unknown(vec![1, 3, 4, 7])); + grid.get(7, 1) + .unwrap() + .set_value(CellValue::Unknown(vec![1, 2, 3, 9])); + grid.get(8, 1) + .unwrap() + .set_value(CellValue::Unknown(vec![1, 2, 3, 5, 9])); // 5 is wrongly removed here - grid.get(1, 0).unwrap().set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9])); + grid.get(1, 0) + .unwrap() + .set_value(CellValue::Unknown(vec![1, 3, 4, 5, 9])); println!("{}", grid); @@ -959,8 +1094,10 @@ mod tests { search_useful_constraint::search_useful_constraint(&grid, line); - assert_eq!(CellValue::Unknown(vec![1, 3, 4, 5, 9]), grid.get(1, 0).unwrap().get_value_copy()); - + assert_eq!( + CellValue::Unknown(vec![1, 3, 4, 5, 9]), + grid.get(1, 0).unwrap().get_value_copy() + ); } #[test] @@ -985,10 +1122,13 @@ mod tests { search_hidden_single(&first_row); - assert_eq!(CellValue::Fixed(1), grid.get(0, 0).unwrap().get_value_copy()); - assert_eq!(CellValue::Fixed(2), grid.get(0, 2).unwrap().get_value_copy()); - + assert_eq!( + CellValue::Fixed(1), + grid.get(0, 0).unwrap().get_value_copy() + ); + assert_eq!( + CellValue::Fixed(2), + grid.get(0, 2).unwrap().get_value_copy() + ); } - - -} \ No newline at end of file +}