diff --git a/src/cell.rs b/src/cell.rs index ca6cd6e..41f04d7 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -1,5 +1,6 @@ use std::fmt; use std::collections::HashSet; +use std::collections::HashMap; use rand::thread_rng; use rand::Rng; @@ -23,6 +24,12 @@ pub enum RemoveResult { Filled, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BlockingCell { + pub state: usize, + pub position: (usize, usize), +} + impl fmt::Display for CellError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -33,32 +40,37 @@ impl fmt::Display for CellError { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Cell { state: Option, - all_states: Vec, - allowed_states: Vec, + blocking_states: HashMap>, } impl Cell { pub fn new(states: Vec) -> Self { + let mut blocking_states = HashMap::new(); + for state in states { + blocking_states.insert(state, vec![]); + } Self { state: None, - all_states: states.clone(), - allowed_states: states, + blocking_states, } } - pub fn get(&self) -> Option { - return self.state + pub fn get_state(&self) -> Option { + self.state } pub fn get_allowed(&self) -> Vec { - return self.allowed_states.clone() + self.blocking_states + .iter() + .filter_map(|(state, blocking)| if blocking.is_empty() { Some(*state) } else { None }) + .collect() } pub fn get_num_allowed(&self) -> usize { - return self.allowed_states.len() + return self.get_allowed().len() } pub fn is_none(&self) -> bool { @@ -69,52 +81,55 @@ impl Cell { if !self.state.is_none() { return Err(CellError::StateAlreadySet) } - if self.allowed_states.len() == 0 { + + let allowed_states = self.get_allowed(); + + if allowed_states.len() == 0 { return Err(CellError::NoAllowedState) } if let CollapseOption::Set(state) = option { - if !self.allowed_states.contains(&state) { + if !allowed_states.contains(&state) { return Err(CellError::StateNotAllowed) } self.state = Some(*state); return Ok(*state) } let choice: usize = if let CollapseOption::Random = option { - thread_rng().gen_range(0..self.allowed_states.len()) + thread_rng().gen_range(0..allowed_states.len()) } else { 0 }; - self.state = Some(self.allowed_states[choice]); - return Ok(self.allowed_states[choice]) + self.state = Some(allowed_states[choice]); + return Ok(allowed_states[choice]) } - pub fn remove_allowed(&mut self, states: &HashSet) -> Result { + pub fn remove_allowed(&mut self, blocking_cells: &Vec) -> Result { if !self.state.is_none() { return Ok(RemoveResult::Filled) } - self.allowed_states.retain(|&x| !states.contains(&x)); - - if self.allowed_states.len() == 0 { - return Err(CellError::NoAllowedState) - } + for blocking_cell in blocking_cells { + if let Some(blocking) = self.blocking_states.get_mut(&blocking_cell.state) { + blocking.push(blocking_cell.position); + } + } - if self.allowed_states.len() == 1 { - self.state = Some(self.allowed_states[0]); - return Ok(RemoveResult::Collapsed(self.allowed_states[0])) - } - return Ok(RemoveResult::NumAllowed(self.allowed_states.len())) + let allowed_states = self.get_allowed(); + + match allowed_states.len() { + 0 => Err(CellError::NoAllowedState), + 1 => { + self.state = Some(allowed_states[0]); + Ok(RemoveResult::Collapsed(allowed_states[0])) + }, + _ => Ok(RemoveResult::NumAllowed(allowed_states.len())), + } } - pub fn add_allowed(&mut self, state: usize) { - if self.allowed_states.contains(&state) || !self.all_states.contains(&state) { - return - } - self.allowed_states.push(state); - } - - pub fn reset_allowed(&mut self) { - self.allowed_states = self.all_states.clone(); + pub fn add_allowed(&mut self, removed_cell: &BlockingCell) { + if let Some(blocking) = self.blocking_states.get_mut(&removed_cell.state) { + blocking.retain(|x| x != &removed_cell.position); + } } pub fn reset_state(&mut self) { diff --git a/src/main.rs b/src/main.rs index dda29e8..7cf643d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,7 @@ use std::process; use clap::{Parser}; mod cell; -use cell::Cell; -use cell::CollapseOption; - mod ui; -use ui::DisplayMode; enum WaveError { Contradiction, @@ -27,27 +23,27 @@ impl fmt::Display for WaveError { } struct Step { - cell_selected: [usize; 2], + cell_selected: (usize, usize), state_selected: usize, num_allowed_states: usize, } struct Sudoku { - grid: Vec>, + grid: Vec>, history: Vec, last_history: usize, size: usize, square_size: usize, debug_display: bool, grid_display: bool, - collapse_option: CollapseOption, + collapse_option: cell::CollapseOption, } impl Sudoku { fn new(order: usize) -> Self { let size = order*order; let states = (1..=size).collect(); - let sudoku_grid: Vec> = vec![vec![Cell::new(states); size]; size]; + let sudoku_grid: Vec> = vec![vec![cell::Cell::new(states); size]; size]; Self { grid: sudoku_grid, history: vec![], @@ -56,36 +52,38 @@ impl Sudoku { square_size: order, debug_display: false, grid_display: false, - collapse_option: CollapseOption::Random, + collapse_option: cell::CollapseOption::Random, } } fn update_possibilities(&mut self) -> Result<(), WaveError> { - let mut row_used_values: Vec> = vec![HashSet::new(); self.size]; - let mut column_used_values: Vec> = vec![HashSet::new(); self.size]; - let mut square_used_values: Vec>> = vec![vec![HashSet::new(); self.square_size]; self.square_size]; + let mut row_used_values: Vec> = vec![vec![]; self.size]; + let mut column_used_values: Vec> = vec![vec![]; self.size]; + let mut square_used_values: Vec>> = vec![vec![vec![]; self.square_size]; self.square_size]; for row_index in 0..self.size { for column_index in 0..self.size { - let Some(value) = self.grid[row_index][column_index].get() else { + let Some(value) = self.grid[row_index][column_index].get_state() else { continue }; - if row_used_values[row_index].contains(&value) || column_used_values[column_index].contains(&value) || column_used_values[column_index].contains(&value) { - return Err(WaveError::Contradiction) - } - row_used_values[row_index].insert(value); - column_used_values[column_index].insert(value); - square_used_values[row_index/self.square_size][column_index/self.square_size].insert(value); + let blocking_cell = cell::BlockingCell { + state: value, + position: (row_index, column_index), + }; + row_used_values[row_index].push(blocking_cell.clone()); + column_used_values[column_index].push(blocking_cell.clone()); + square_used_values[row_index/self.square_size][column_index/self.square_size].push(blocking_cell); } } for row_index in 0..self.size { - for column_index in 0..self.size { - let mut used_values = row_used_values[row_index].clone(); - used_values.extend(&column_used_values[column_index]); - used_values.extend(&square_used_values[row_index/self.square_size][column_index/self.square_size]); - - self.remove_allowed(row_index, column_index, &used_values)?; + for column_index in 0..self.size { + self.remove_allowed(row_index, column_index, &row_used_values[row_index] + .iter() + .chain(&column_used_values[column_index]) + .chain(&square_used_values[row_index / self.square_size][column_index / self.square_size]) + .cloned() + .collect())?; } } Ok(()) @@ -99,8 +97,8 @@ impl Sudoku { return Ok(()) } - let collapsed_row = self.history[self.last_history].cell_selected[0]; - let collapsed_column = self.history[self.last_history].cell_selected[1]; + let collapsed_row = self.history[self.last_history].cell_selected.0; + let collapsed_column = self.history[self.last_history].cell_selected.1; let collapsed_state = self.history[self.last_history].state_selected; self.last_history += 1; @@ -108,8 +106,10 @@ impl Sudoku { println!("- propagating {}", collapsed_state); } - let mut collapsed_possibility = HashSet::new(); - collapsed_possibility.insert(collapsed_state); + let collapsed_possibility = vec![cell::BlockingCell { + state: collapsed_state, + position: (collapsed_row, collapsed_column), + }]; for index in 0..self.size { if index != collapsed_column { @@ -130,11 +130,10 @@ impl Sudoku { Ok(()) } - fn check_impossible(&self, collapsed_row: usize, collapsed_column: usize) -> Result<(),WaveError> { - + fn check_impossible(&self, collapsed_row: usize, collapsed_column: usize) -> Result<(),WaveError> { let mut missing_states: HashSet = (1..=self.size).collect(); for column_index in 0..self.size { - if let Some(state) = self.grid[collapsed_row][column_index].get() { + if let Some(state) = self.grid[collapsed_row][column_index].get_state() { missing_states.remove(&state); continue } @@ -150,7 +149,7 @@ impl Sudoku { } missing_states = (1..=self.size).collect(); for row_index in 0..self.size { - if let Some(state) = self.grid[row_index][collapsed_column].get() { + if let Some(state) = self.grid[row_index][collapsed_column].get_state() { missing_states.remove(&state); continue } @@ -169,7 +168,7 @@ impl Sudoku { for column_index in 0..self.square_size { let row = (collapsed_row/self.square_size)*self.square_size + row_index; let column = (collapsed_column/self.square_size)*self.square_size + column_index; - if let Some(state) = self.grid[row][column].get() { + if let Some(state) = self.grid[row][column].get_state() { missing_states.remove(&state); continue } @@ -223,14 +222,19 @@ impl Sudoku { let mut fork: Option = None; while let Some(step) = self.history.pop() { self.last_history -= 1; - self.grid[step.cell_selected[0]][step.cell_selected[1]].reset_state(); - self.propagate_backtrack(step.cell_selected, step.state_selected); + self.grid[step.cell_selected.0][step.cell_selected.1].reset_state(); + + let blocking_cell = cell::BlockingCell { + state: step.state_selected, + position: step.cell_selected, + }; + self.propagate_backtrack(step.cell_selected, blocking_cell); if step.num_allowed_states > 1 { fork = Some(step); break; } if self.debug_display { - println!("* backtracking [{}][{}] : {}", step.cell_selected[0], step.cell_selected[1], step.state_selected); + println!("* backtracking [{}][{}] : {}", step.cell_selected.0, step.cell_selected.1, step.state_selected); } } @@ -241,52 +245,49 @@ impl Sudoku { return Err(WaveError::NoHistory) }; if self.debug_display { - println!("* fork [{}][{}] : {}", step.cell_selected[0], step.cell_selected[1], step.state_selected); + println!("* fork [{}][{}] : {}", step.cell_selected.0, step.cell_selected.1, step.state_selected); } - - //self.reset_allowed(); let mut state_selected_set = HashSet::new(); state_selected_set.insert(step.state_selected); - self.remove_allowed(step.cell_selected[0], step.cell_selected[1], &state_selected_set)?; + let blocking_cell = cell::BlockingCell { + state: step.state_selected, + position: step.cell_selected, + }; + + self.remove_allowed(step.cell_selected.0, step.cell_selected.1, &vec![blocking_cell])?; if self.debug_display { - println!(" - removed : {}, available : {:?}", step.state_selected, self.grid[step.cell_selected[0]][step.cell_selected[1]].get_allowed()); + println!(" - removed : {}, available : {:?}", step.state_selected, self.grid[step.cell_selected.0][step.cell_selected.1].get_allowed()); } Ok(()) } - fn propagate_backtrack(&mut self, cell_pos: [usize; 2], removed_state: usize) { + fn propagate_backtrack(&mut self, cell_pos: (usize, usize), removed_cell: cell::BlockingCell) { for index in 0..self.size { - if index != cell_pos[0] { - self.grid[index][cell_pos[1]].add_allowed(removed_state); + if index != cell_pos.0 { + self.grid[index][cell_pos.1].add_allowed(&removed_cell); } - if index != cell_pos[1] { - self.grid[cell_pos[0]][index].add_allowed(removed_state); + if index != cell_pos.1 { + self.grid[cell_pos.0][index].add_allowed(&removed_cell); } - let square_row = (cell_pos[0]/self.square_size)*self.square_size + index/self.square_size; - let square_column = (cell_pos[1]/self.square_size)*self.square_size + index%self.square_size; - if square_row != cell_pos[0] || square_column != cell_pos[1] { - self.grid[square_row][square_column].add_allowed(removed_state); + let square_row = (cell_pos.0/self.square_size)*self.square_size + index/self.square_size; + let square_column = (cell_pos.1/self.square_size)*self.square_size + index%self.square_size; + if square_row != cell_pos.0 || square_column != cell_pos.1 { + self.grid[square_row][square_column].add_allowed(&removed_cell); } } } - - fn reset_allowed(&mut self) { - for row in &mut self.grid { - for cell in row { - cell.reset_allowed(); - } - } - } - fn remove_allowed(&mut self, row_index: usize, column_index: usize, set_to_remove: &HashSet) -> Result<(), WaveError> { - if let Some(state) = self.grid[row_index][column_index].get() { - if set_to_remove.contains(&state) { - return Err(WaveError::Contradiction) + fn remove_allowed(&mut self, row_index: usize, column_index: usize, blocking_cells: &Vec) -> Result<(), WaveError> { + if let Some(state) = self.grid[row_index][column_index].get_state() { + for blocking_cell in blocking_cells { + if blocking_cell.state == state { + return Err(WaveError::Contradiction) + } } } - match self.grid[row_index][column_index].remove_allowed(set_to_remove) { + match self.grid[row_index][column_index].remove_allowed(blocking_cells) { Ok(result) => { let cell::RemoveResult::Collapsed(state) = result else { return Ok(()) @@ -295,7 +296,7 @@ impl Sudoku { println!("* collapsed by removal [{}][{}] to {}", row_index, column_index, state) } self.history.push(Step { - cell_selected: [row_index, column_index], + cell_selected: (row_index, column_index), state_selected: state, num_allowed_states: 1, }); @@ -318,7 +319,7 @@ impl Sudoku { println!("# collapsing [{}][{}] ({}) to {}", row_index, column_index, num_allowed_states, state_selected); } self.history.push(Step { - cell_selected: [row_index, column_index], + cell_selected: (row_index, column_index), state_selected, num_allowed_states, }); @@ -335,7 +336,7 @@ impl Sudoku { fn solve(&mut self, solver_limit: Option) -> Result<(), WaveError> { let now = Instant::now(); - self.display(DisplayMode::Full); + self.display(ui::DisplayMode::Full); let mut n_start_cells: usize = 0; for row in &self.grid { for cell in row { @@ -372,22 +373,12 @@ impl Sudoku { while backtrack > 0 { backtrack -=1; self.backtrack()?; - // match self.update_possibilities() { - // Ok(_) => {}, - // Err(reason) => { - // if let WaveError::Contradiction = reason { - // backtrack += 1; - // } else { - // return Err(reason) - // } - // } - // }; } propagation_counter += 1; } if self.grid_display { - self.display(DisplayMode::Full); + self.display(ui::DisplayMode::Full); } self.collapse()?; @@ -413,15 +404,15 @@ impl Sudoku { } let elapsed = now.elapsed(); println!("# finished in {} propagations ({} forced collapse), {:.2?} ({:.2?}/propagation)", propagation_counter, collapse_counter, elapsed, elapsed/(propagation_counter as u32)); - self.display(DisplayMode::Full); + self.display(ui::DisplayMode::Full); Ok(()) } fn random_mode(&mut self, collapse_random: bool) { if collapse_random { - self.collapse_option = CollapseOption::Random; + self.collapse_option = cell::CollapseOption::Random; } else { - self.collapse_option = CollapseOption::First + self.collapse_option = cell::CollapseOption::First } } } @@ -471,6 +462,6 @@ fn main() { if let Err(reason) = sudoku.solve(args.limit) { println!("{}", reason); - sudoku.display(DisplayMode::Full); + sudoku.display(ui::DisplayMode::Full); } }