Compare commits

..

10 commits

Author SHA1 Message Date
WanderingPenwing 28cd9c2b8e readme 2024-12-06 14:52:08 +01:00
WanderingPenwing 684ab1565f basic puzzle 2024-12-06 14:48:27 +01:00
WanderingPenwing 4be954b9d0 puzzle mode (not completed) 2024-12-05 23:02:01 +01:00
WanderingPenwing 78fc4bc205 benchmark progress bar 2024-12-05 22:25:28 +01:00
WanderingPenwing 9dd739ffcc added benchmark 2024-12-04 17:57:03 +01:00
WanderingPenwing 2f3555b303 detect impossible puzzle 2024-12-04 17:10:35 +01:00
WanderingPenwing 855472e42a added better time measurement 2024-12-04 13:13:55 +01:00
WanderingPenwing f9dda499e2 removed global update 2024-12-03 22:40:35 +01:00
WanderingPenwing 3e3245a977 split clean 2024-12-03 22:23:48 +01:00
WanderingPenwing 2678a5d957 split files 2024-12-03 20:10:45 +01:00
8 changed files with 607 additions and 386 deletions

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# Jiro
(from the wind rises)
A sudoku solver (can take blank input) for size n

View file

@ -1,326 +1,7 @@
use std::fmt;
use std::collections::HashSet;
use std::time::Instant;
use clap::{Parser};
mod cell;
mod ui;
mod solver;
enum WaveError {
Contradiction,
NoHistory,
}
impl fmt::Display for WaveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WaveError::Contradiction => write!(f, "Error: The puzzle contradicts itself."),
WaveError::NoHistory => write!(f, "Error: Tried to backtrack but the History is empty."),
}
}
}
#[derive(Debug, Clone)]
struct Step {
position: (usize, usize),
state_set: usize,
num_allowed_states: usize,
}
impl fmt::Display for Step {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({},{}) to {}, out of {}", self.position.0, self.position.1, self.state_set, self.num_allowed_states)
}
}
struct Solver {
grid: Vec<Vec<cell::Cell>>,
history: Vec<Step>,
last_move_index: usize,
size: usize,
square_size: usize,
debug_display: bool,
grid_display: bool,
collapse_option: cell::CollapseOption,
}
impl Solver {
fn new(order: usize) -> Self {
let size = order*order;
let states = (1..=size).collect();
let sudoku_grid: Vec<Vec<cell::Cell>> = vec![vec![cell::Cell::new(states); size]; size];
Self {
grid: sudoku_grid,
history: vec![],
last_move_index: 0,
size,
square_size: order,
debug_display: false,
grid_display: false,
collapse_option: cell::CollapseOption::Random,
}
}
fn update_possibilities(&mut self) -> Result<(), WaveError> {
let mut row_used_states: Vec<Vec<cell::BlockingCell>> = vec![vec![]; self.size];
let mut column_used_states: Vec<Vec<cell::BlockingCell>> = vec![vec![]; self.size];
let mut square_used_states: Vec<Vec<Vec<cell::BlockingCell>>> = 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(state) = self.grid[row_index][column_index].get_state() else {
continue
};
let blocking_cell = cell::BlockingCell {
state,
position: (row_index, column_index),
};
row_used_states[row_index].push(blocking_cell.clone());
column_used_states[column_index].push(blocking_cell.clone());
square_used_states[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 {
self.remove_allowed((row_index, column_index), &row_used_states[row_index]
.iter()
.chain(&column_used_states[column_index])
.chain(&square_used_states[row_index / self.square_size][column_index / self.square_size])
.cloned()
.collect())?;
}
}
Ok(())
}
fn propagate_collapse(&mut self) -> Result<(), WaveError> {
if self.last_move_index >= self.history.len() {
self.debug(&format!("x nothing to propagate"));
return Ok(())
}
let last_move = self.history[self.last_move_index].clone();
self.last_move_index += 1;
self.debug(&format!("- propagating {}", last_move));
let collapsed_possibility = vec![cell::BlockingCell {
state: last_move.state_set,
position: last_move.position,
}];
for index in 0..self.size {
if index != last_move.position.1 {
self.remove_allowed((last_move.position.0, index), &collapsed_possibility)?;
}
if index != last_move.position.0 {
self.remove_allowed((index, last_move.position.1), &collapsed_possibility)?;
}
let square_position = ((last_move.position.0/self.square_size)*self.square_size + index/self.square_size,
(last_move.position.1/self.square_size)*self.square_size + index%self.square_size);
if square_position.0 != last_move.position.0 || square_position.1 != last_move.position.1 {
self.remove_allowed(square_position, &collapsed_possibility)?;
}
}
self.check_impossible(last_move.position)?;
Ok(())
}
fn check_impossible(&self, center_position: (usize, usize)) -> Result<(),WaveError> {
let mut missing_states: HashSet<usize> = (1..=self.size).collect();
for column in 0..self.size {
let existing_states = self.grid[center_position.0][column].get_existing();
missing_states.retain(|state| !existing_states.contains(state));
}
if !missing_states.is_empty() {
self.debug(&format!("x missing row state : {:?}", missing_states));
return Err(WaveError::Contradiction)
}
missing_states = (1..=self.size).collect();
for row in 0..self.size {
let existing_states = self.grid[row][center_position.1].get_existing();
missing_states.retain(|state| !existing_states.contains(state));
}
if !missing_states.is_empty() {
self.debug(&format!("x missing column state : {:?}", missing_states));
return Err(WaveError::Contradiction)
}
missing_states = (1..=self.size).collect();
for square_index in 0..self.size {
let row = (center_position.0/self.square_size)*self.square_size + square_index/self.square_size;
let column = (center_position.1/self.square_size)*self.square_size + square_index%self.square_size;
let existing_states = self.grid[row][column].get_existing();
missing_states.retain(|state| !existing_states.contains(state));
}
if !missing_states.is_empty() {
self.debug(&format!("x missing square state : {:?}", missing_states));
return Err(WaveError::Contradiction)
}
Ok(())
}
fn collapse(&mut self) -> Result<(), WaveError> {
let mut min_allowed_position: (usize, usize) = (0, 0);
let mut min_allowed_number: usize = self.size;
let mut grid_has_empty_cell: bool = false;
for row_index in 0..self.size {
for column_index in 0..self.size {
if !self.grid[row_index][column_index].is_none() {
continue;
}
grid_has_empty_cell = true;
let possibilities_len = self.grid[row_index][column_index].get_num_allowed();
if possibilities_len < min_allowed_number {
min_allowed_position = (row_index, column_index);
min_allowed_number = possibilities_len;
}
}
}
if !grid_has_empty_cell {
self.debug("x no empty cells");
return Ok(())
}
return self.collapse_cell(min_allowed_position)
}
fn backtrack(&mut self) -> Result<(), WaveError> {
let mut fork: Option<Step> = None;
while let Some(step) = self.history.pop() {
self.last_move_index -= 1;
self.grid[step.position.0][step.position.1].reset_state();
let blocking_cell = cell::BlockingCell {
state: step.state_set,
position: step.position,
};
self.propagate_backtrack(step.position, blocking_cell);
if step.num_allowed_states > 1 {
fork = Some(step);
break;
}
self.debug(&format!("* backtracking [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
}
let Some(step) = fork else {
self.debug("x backtracked to start");
return Err(WaveError::NoHistory)
};
self.debug(&format!("* fork [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
let mut state_selected_set = HashSet::new();
state_selected_set.insert(step.state_set);
let blocking_cell = cell::BlockingCell {
state: step.state_set,
position: step.position,
};
self.remove_allowed(step.position, &vec![blocking_cell])?;
self.debug(&format!("- removed : {}, available : {:?}", step.state_set, self.grid[step.position.0][step.position.1].get_allowed()));
Ok(())
}
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_cell);
}
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_cell);
}
}
}
fn solve(&mut self, solver_limit: Option<usize>) -> Result<(), WaveError> {
let now = Instant::now();
self.display(ui::DisplayMode::Full);
let mut number_cell_init: usize = 0;
for row in &self.grid {
for cell in row {
if !cell.is_none() {
number_cell_init += 1;
}
}
}
let mut propagation_counter: usize = 0;
let mut collapse_counter: usize = 0;
println!("# started");
self.debug("--------");
self.update_possibilities()?;
while number_cell_init + self.history.len() < self.size * self.size {
self.debug(&format!("\n## while, h={}/{}", self.last_move_index, self.history.len()));
while self.last_move_index < self.history.len() && number_cell_init + self.history.len() < self.size * self.size {
let mut backtrack = 0;
match self.propagate_collapse() {
Ok(_) => {},
Err(reason) => {
if let WaveError::Contradiction = reason {
backtrack = 1;
} else {
return Err(reason)
}
}
};
while backtrack > 0 {
backtrack -=1;
self.backtrack()?;
}
propagation_counter += 1;
}
if self.grid_display {
if !self.debug_display {
self.display(ui::DisplayMode::Erase);
} else {
self.display(ui::DisplayMode::Full);
}
}
self.collapse()?;
collapse_counter += 1;
if !self.debug_display && !self.grid_display {
self.progress_bar(number_cell_init);
}
if let Some(limit) = solver_limit {
if collapse_counter >= limit {
break;
}
}
}
self.debug("--------");
let elapsed = now.elapsed();
println!("\n# finished in {} propagations ({} forced collapse), {:.2?} ({:.2?}/propagation)", propagation_counter, collapse_counter, elapsed, elapsed/(propagation_counter as u32));
if !self.grid_display || self.debug_display {
self.display(ui::DisplayMode::Full);
}
Ok(())
}
fn random_mode(&mut self, collapse_random: bool) {
if collapse_random {
self.collapse_option = cell::CollapseOption::Random;
} else {
self.collapse_option = cell::CollapseOption::First
}
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
@ -340,6 +21,10 @@ struct Args {
#[arg(long)]
limit: Option<usize>, // Optional value
/// Solve n times and averages the results
#[arg(long)]
benchmark: Option<usize>, // Optional value
/// Disable randomization of the collapse
#[arg(long)]
norand: bool,
@ -347,13 +32,17 @@ struct Args {
/// Display grid when solving
#[arg(long)]
grid: bool,
/// Create puzzle
#[arg(long)]
puzzle: Option<usize>,
}
fn main() {
let args = Args::parse();
let mut solver = Solver::new(args.size);
let mut solver = solver::Solver::new(args.size);
if args.ask {
if let Err(reason) = solver.ask() {
@ -364,8 +53,21 @@ fn main() {
solver.random_mode(!args.norand);
solver.grid_display_mode(args.grid);
if let Err(reason) = solver.solve(args.limit) {
if let Some(number) = args.benchmark {
solver.benchmark(number);
return
}
let result = if let Some(difficulty) = args.puzzle {
solver.make_puzzle(difficulty).map(|_| ())
} else {
solver.solve(args.limit).map(|_| ())
};
if let Err(reason) = result {
println!("{}", reason);
solver.display(ui::DisplayMode::Full);
if args.debug {
solver.display(solver::ui::DisplayMode::Full);
}
}
}

View file

@ -1,42 +0,0 @@
use crate::Solver;
use crate::WaveError;
use crate::Step;
use crate::cell;
impl Solver {
pub fn remove_allowed(&mut self, position: (usize, usize), blocking_cells: &Vec<cell::BlockingCell>) -> Result<(), WaveError> {
match self.grid[position.0][position.1].remove_allowed(blocking_cells) {
Ok(result) => {
let cell::RemoveResult::Collapsed(state_set) = result else {
return Ok(())
};
self.debug(&format!("* collapsed by removal ({},{}) to {}", position.0, position.1, state_set));
self.history.push(Step {
position,
state_set,
num_allowed_states: 1,
});
return Ok(())
}
Err(reason) => {
self.debug(&format!("x failed to update states allowed of ({},{}) : {}", position.0, position.1, reason));
return Err(WaveError::Contradiction)
}
}
}
pub fn collapse_cell(&mut self, position: (usize, usize)) -> Result<(), WaveError> {
match self.grid[position.0][position.1].collapse(&self.collapse_option) {
Ok(state_set) => {
let num_allowed_states = self.grid[position.0][position.1].get_num_allowed();
self.debug(&format!("# collapsing ({},{}) ({}) to {}", position.0, position.1, num_allowed_states, state_set));
self.history.push(Step {position, state_set, num_allowed_states});
return Ok(())
}
Err(reason) => {
self.debug(&format!("x could not collapse [{}][{}] : {}", position.0, position.1, reason));
return Err(WaveError::Contradiction)
}
}
}
}

View file

@ -57,10 +57,6 @@ impl Cell {
}
}
pub fn get_state(&self) -> Option<usize> {
self.state
}
pub fn get_allowed(&self) -> Vec<usize> {
self.blocking_states
.iter()
@ -68,6 +64,10 @@ impl Cell {
.collect()
}
pub fn get_state(&self) -> Option<usize> {
self.state
}
pub fn get_existing(&self) -> Vec<usize> {
if let Some(state) = self.state {
vec![state]
@ -111,16 +111,19 @@ impl Cell {
}
pub fn remove_allowed(&mut self, blocking_cells: &Vec<BlockingCell>) -> Result<RemoveResult, CellError> {
if !self.state.is_none() {
return Ok(RemoveResult::Filled)
}
for blocking_cell in blocking_cells {
if let Some(blocking) = self.blocking_states.get_mut(&blocking_cell.state) {
blocking.push(blocking_cell.position);
}
}
if let Some(state) = self.state {
if self.blocking_states[&state].len() > 0 {
return Err(CellError::StateNotAllowed)
}
return Ok(RemoveResult::Filled)
}
let allowed_states = self.get_allowed();
match allowed_states.len() {

434
src/solver/mod.rs Normal file
View file

@ -0,0 +1,434 @@
use std::collections::HashSet;
use std::time::Instant;
use std::time::Duration;
use std::ops::MulAssign;
use std::fmt;
use rand::Rng;
pub mod sudoku;
pub mod ui;
mod cell;
pub enum WaveError {
Contradiction,
NoHistory,
}
impl fmt::Display for WaveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WaveError::Contradiction => write!(f, "Error: There is a contradiction in the puzzle."),
WaveError::NoHistory => write!(f, "Error: The puzzle is impossible (backtracked to start)."),
}
}
}
#[derive(Debug, Clone)]
struct Step {
position: (usize, usize),
state_set: usize,
num_allowed_states: usize,
}
impl fmt::Display for Step {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({},{}) to {}, out of {}", self.position.0, self.position.1, self.state_set, self.num_allowed_states)
}
}
pub struct SolveResult {
total_time: Duration,
propagation: (usize, Duration),
collapse: (usize, Duration),
backtrack: (usize, Duration),
}
impl fmt::Display for SolveResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let propagation_percentage = (self.propagation.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0;
let collapse_percentage = (self.collapse.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0;
let backtrack_percentage = (self.backtrack.1.as_secs_f64() / self.total_time.as_secs_f64()) * 100.0;
write!(f, "# total time : {:.2?} ({:.2?}% propagation, {:.2?}% forced collapse, {:.2?}% backtrack)\n- {} propagations ({:.2?}), {} forced collapse ({:.2?}), {} backtrack ({:.2?})",
self.total_time, propagation_percentage, collapse_percentage, backtrack_percentage,
self.propagation.0, (self.propagation.1.checked_div(self.propagation.0 as u32)).unwrap_or(std::time::Duration::ZERO),
self.collapse.0, (self.collapse.1.checked_div(self.collapse.0 as u32)).unwrap_or(std::time::Duration::ZERO),
self.backtrack.0, (self.backtrack.1.checked_div(self.backtrack.0 as u32)).unwrap_or(std::time::Duration::ZERO)
)
}
}
impl std::ops::AddAssign for SolveResult {
fn add_assign(&mut self, other: Self) {
self.total_time += other.total_time;
self.propagation.0 += other.propagation.0;
self.propagation.1 += other.propagation.1;
self.collapse.0 += other.collapse.0;
self.collapse.1 += other.collapse.1;
self.backtrack.0 += other.backtrack.0;
self.backtrack.1 += other.backtrack.1;
}
}
impl MulAssign<f32> for SolveResult {
fn mul_assign(&mut self, factor: f32) {
// Scale the durations
self.total_time = scale_duration(self.total_time, factor);
self.propagation.1 = scale_duration(self.propagation.1, factor);
self.collapse.1 = scale_duration(self.collapse.1, factor);
self.backtrack.1 = scale_duration(self.backtrack.1, factor);
// Scale the usize counts (rounded to nearest integer)
self.propagation.0 = scale_usize(self.propagation.0, factor);
self.collapse.0 = scale_usize(self.collapse.0, factor);
self.backtrack.0 = scale_usize(self.backtrack.0, factor);
}
}
impl Default for SolveResult {
fn default() -> Self {
Self {
total_time: Duration::ZERO,
propagation: (0, Duration::ZERO),
collapse: (0, Duration::ZERO),
backtrack: (0, Duration::ZERO),
}
}
}
// Helper to scale `Duration` by `f32`
fn scale_duration(duration: Duration, factor: f32) -> Duration {
let nanos = duration.as_secs_f64() * (factor as f64);
Duration::from_secs_f64(nanos)
}
// Helper to scale `usize` by `f32`
fn scale_usize(value: usize, factor: f32) -> usize {
((value as f64) * (factor as f64)).round() as usize
}
pub struct Solver {
grid: Vec<Vec<cell::Cell>>,
history: Vec<Step>,
last_move_index: usize,
size: usize,
square_size: usize,
debug_display: bool,
grid_display: bool,
solve_display: bool,
solve_progress_display: bool,
collapse_option: cell::CollapseOption,
}
impl Solver {
pub fn new(order: usize) -> Self {
let size = order*order;
let states = (1..=size).collect();
let sudoku_grid: Vec<Vec<cell::Cell>> = vec![vec![cell::Cell::new(states); size]; size];
Self {
grid: sudoku_grid,
history: vec![],
last_move_index: 0,
size,
square_size: order,
debug_display: false,
grid_display: false,
solve_display: true,
solve_progress_display: true,
collapse_option: cell::CollapseOption::Random,
}
}
pub fn make_puzzle(&mut self, difficulty: usize) -> Result<(), WaveError> {
self.solve_display = false;
println!("\n# generating full...");
let _ = self.solve(None)?;
println!("\n\n# erasing cells...");
let mut rng = rand::thread_rng();
let mut min_num_allowed = 1;
while min_num_allowed <= difficulty {
let erase_cell = loop {
let pos = (rng.gen_range(0..self.size), rng.gen_range(0..self.size));
if let Some(state) = self.grid[pos.0][pos.1].get_state() {
self.grid[pos.0][pos.1].reset_state();
break (pos, state);
}
};
let blocking_cell = cell::BlockingCell {
state: erase_cell.1,
position: erase_cell.0,
};
min_num_allowed = self.propagate_backtrack(erase_cell.0, blocking_cell);
if min_num_allowed > difficulty {
let _ = self.grid[erase_cell.0.0][erase_cell.0.1].collapse(&cell::CollapseOption::Set(erase_cell.1));
//println!("put back ({},{}) to {}", erase_cell.0.0, erase_cell.0.1, erase_cell.1);
//self.propagate_collapse(erase_cell.0, blocking_cell);
}
}
println!();
self.display(ui::DisplayMode::Full);
self.solve_display = true;
Ok(())
}
fn reset_grid(&mut self) {
let states = (1..=self.size).collect();
let empty_grid = vec![vec![cell::Cell::new(states); self.size]; self.size];
self.grid = empty_grid.clone();
self.history = vec![];
self.last_move_index = 0;
}
pub fn random_mode(&mut self, collapse_random: bool) {
if collapse_random {
self.collapse_option = cell::CollapseOption::Random;
} else {
self.collapse_option = cell::CollapseOption::First
}
}
pub fn benchmark(&mut self, solve_number: usize) {
self.solve_display = false;
self.solve_progress_display = false;
println!();
if self.grid_display {
self.display(ui::DisplayMode::Full);
}
let mut results_sum: SolveResult = SolveResult::default();
let mut error_number: usize = 0;
for index in 0..solve_number {
self.reset_grid();
match self.solve(None) {
Ok(result) => {
if self.debug_display {
println!("\n{}", result);
}
results_sum += result;
}
Err(reason) => {
if self.debug_display {
println!("{}", reason);
}
error_number += 1;
}
}
if !self.debug_display && !self.grid_display {
let progress = (index + 1) as f32 / solve_number as f32;
self.progress_bar(progress);
}
if self.grid_display {
if !self.debug_display {
self.display(ui::DisplayMode::Erase);
} else {
self.display(ui::DisplayMode::Full);
}
}
}
if self.debug_display {
println!("\n---------");
} else {
println!();
}
results_sum *= 1.0 / ((solve_number - error_number) as f32);
println!("\nbenchmark {} (n = {})\n{}\n({} errors)", self.square_size, solve_number, results_sum, error_number);
self.solve_display = true;
self.solve_progress_display = true;
}
pub fn solve(&mut self, solver_limit: Option<usize>) -> Result<SolveResult, WaveError> {
let start_time = Instant::now();
if self.solve_display {
self.display(ui::DisplayMode::Full);
}
let mut propagation_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO);
let mut collapse_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO);
let mut backtrack_counter: (usize, std::time::Duration) = (0, std::time::Duration::ZERO);
self.debug("--------");
while self.history.len() < self.size * self.size {
self.debug(&format!("\n## while, h={}/{}", self.last_move_index, self.history.len()));
while self.last_move_index < self.history.len() && self.history.len() < self.size * self.size {
let mut need_backtrack = false;
let propagation_start = Instant::now();
match self.propagate_collapse() {
Ok(_) => {},
Err(reason) => {
if let WaveError::Contradiction = reason {
need_backtrack = true;
} else {
return Err(reason)
}
}
};
propagation_counter.0 += 1;
propagation_counter.1 += propagation_start.elapsed();
if need_backtrack {
let backtrack_start = Instant::now();
self.backtrack()?;
backtrack_counter.0 += 1;
backtrack_counter.1 += backtrack_start.elapsed();
}
}
if self.grid_display && self.solve_display {
if !self.debug_display {
self.display(ui::DisplayMode::Erase);
} else {
self.display(ui::DisplayMode::Full);
}
}
let collapse_start = Instant::now();
self.collapse()?;
collapse_counter.0 += 1;
collapse_counter.1 += collapse_start.elapsed();
if !self.debug_display && !self.grid_display && self.solve_progress_display {
self.progress_bar(self.history.len() as f32/((self.size*self.size) as f32));
}
if let Some(limit) = solver_limit {
if collapse_counter.0 >= limit {
break;
}
}
}
self.debug("--------");
if (!self.grid_display || self.debug_display) && self.solve_display {
println!();
self.display(ui::DisplayMode::Full);
}
let total_time = start_time.elapsed();
let result = SolveResult {
total_time,
propagation : propagation_counter,
collapse : collapse_counter,
backtrack : backtrack_counter,
};
if self.solve_display {
println!("\n{}", result);
}
Ok(result)
}
fn collapse(&mut self) -> Result<(), WaveError> {
let mut min_allowed_position: (usize, usize) = (0, 0);
let mut min_allowed_number: usize = self.size;
let mut grid_has_empty_cell: bool = false;
for row_index in 0..self.size {
for column_index in 0..self.size {
if !self.grid[row_index][column_index].is_none() {
continue;
}
grid_has_empty_cell = true;
let possibilities_len = self.grid[row_index][column_index].get_num_allowed();
if possibilities_len < min_allowed_number {
min_allowed_position = (row_index, column_index);
min_allowed_number = possibilities_len;
}
}
}
if !grid_has_empty_cell {
self.debug("x no empty cells");
return Ok(())
}
return self.collapse_cell(min_allowed_position)
}
fn collapse_cell(&mut self, position: (usize, usize)) -> Result<(), WaveError> {
match self.grid[position.0][position.1].collapse(&self.collapse_option) {
Ok(state_set) => {
let num_allowed_states = self.grid[position.0][position.1].get_num_allowed();
self.debug(&format!("# collapsing ({},{}) ({}) to {}", position.0, position.1, num_allowed_states, state_set));
self.history.push(Step {position, state_set, num_allowed_states});
return Ok(())
}
Err(reason) => {
self.debug(&format!("x could not collapse [{}][{}] : {}", position.0, position.1, reason));
return Err(WaveError::Contradiction)
}
}
}
fn get_last_fork(&mut self) -> Result<Step, WaveError> {
let mut fork: Option<Step> = None;
while let Some(step) = self.history.pop() {
if self.last_move_index == 0 {
self.debug("there is no last move");
return Err(WaveError::NoHistory)
}
self.last_move_index -= 1;
self.grid[step.position.0][step.position.1].reset_state();
let blocking_cell = cell::BlockingCell {
state: step.state_set,
position: step.position,
};
self.propagate_backtrack(step.position, blocking_cell);
if step.num_allowed_states > 1 {
fork = Some(step);
break;
}
self.debug(&format!("* backtracking [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
}
match fork {
Some(step) => {
self.debug(&format!("* fork [{}][{}] : {}", step.position.0, step.position.1, step.state_set));
Ok(step)
},
None => {
self.debug("x backtracked to start");
Err(WaveError::NoHistory)
}
}
}
fn backtrack(&mut self) -> Result<(), WaveError> {
let step = self.get_last_fork()?;
let mut state_selected_set = HashSet::new();
state_selected_set.insert(step.state_set);
let blocking_cell = cell::BlockingCell {
state: step.state_set,
position: step.position,
};
self.remove_allowed(step.position, &vec![blocking_cell])?;
self.debug(&format!("- removed : {}, available : {:?}", step.state_set, self.grid[step.position.0][step.position.1].get_allowed()));
Ok(())
}
fn remove_allowed(&mut self, position: (usize, usize), blocking_cells: &Vec<cell::BlockingCell>) -> Result<(), WaveError> {
match self.grid[position.0][position.1].remove_allowed(blocking_cells) {
Ok(result) => {
let cell::RemoveResult::Collapsed(state_set) = result else {
return Ok(())
};
self.debug(&format!("* collapsed by removal ({},{}) to {}", position.0, position.1, state_set));
self.history.push(Step {
position,
state_set,
num_allowed_states: 1,
});
return Ok(())
}
Err(reason) => {
self.debug(&format!("x failed to update states allowed of ({},{}) : {}", position.0, position.1, reason));
return Err(WaveError::Contradiction)
}
}
}
}

100
src/solver/sudoku.rs Normal file
View file

@ -0,0 +1,100 @@
use std::collections::HashSet;
use super::Solver;
use super::WaveError;
use super::cell;
impl Solver {
pub fn propagate_collapse(&mut self) -> Result<(), WaveError> {
if self.last_move_index >= self.history.len() {
self.debug(&format!("x nothing to propagate"));
return Ok(())
}
let last_move = self.history[self.last_move_index].clone();
self.last_move_index += 1;
self.debug(&format!("- propagating {}", last_move));
let collapsed_possibility = vec![cell::BlockingCell {
state: last_move.state_set,
position: last_move.position,
}];
for index in 0..self.size {
if index != last_move.position.1 {
self.remove_allowed((last_move.position.0, index), &collapsed_possibility)?;
}
if index != last_move.position.0 {
self.remove_allowed((index, last_move.position.1), &collapsed_possibility)?;
}
let square_position = ((last_move.position.0/self.square_size)*self.square_size + index/self.square_size,
(last_move.position.1/self.square_size)*self.square_size + index%self.square_size);
if square_position.0 != last_move.position.0 || square_position.1 != last_move.position.1 {
self.remove_allowed(square_position, &collapsed_possibility)?;
}
}
self.check_impossible(last_move.position)?;
Ok(())
}
fn check_impossible(&self, center_position: (usize, usize)) -> Result<(),WaveError> {
let mut missing_states: HashSet<usize> = (1..=self.size).collect();
for column in 0..self.size {
let existing_states = self.grid[center_position.0][column].get_existing();
missing_states.retain(|state| !existing_states.contains(state));
}
if !missing_states.is_empty() {
self.debug(&format!("x missing row state : {:?}", missing_states));
return Err(WaveError::Contradiction)
}
missing_states = (1..=self.size).collect();
for row in 0..self.size {
let existing_states = self.grid[row][center_position.1].get_existing();
missing_states.retain(|state| !existing_states.contains(state));
}
if !missing_states.is_empty() {
self.debug(&format!("x missing column state : {:?}", missing_states));
return Err(WaveError::Contradiction)
}
missing_states = (1..=self.size).collect();
for square_index in 0..self.size {
let row = (center_position.0/self.square_size)*self.square_size + square_index/self.square_size;
let column = (center_position.1/self.square_size)*self.square_size + square_index%self.square_size;
let existing_states = self.grid[row][column].get_existing();
missing_states.retain(|state| !existing_states.contains(state));
}
if !missing_states.is_empty() {
self.debug(&format!("x missing square state : {:?}", missing_states));
return Err(WaveError::Contradiction)
}
Ok(())
}
pub fn propagate_backtrack(&mut self, cell_pos: (usize, usize), removed_cell: cell::BlockingCell) -> usize {
let mut min_allowed_number = self.size;
for index in 0..self.size {
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_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_cell);
}
let num_allowed = self.grid[square_row][square_column].get_num_allowed();
if num_allowed < min_allowed_number {
min_allowed_number = num_allowed
}
}
min_allowed_number
}
}

View file

@ -1,7 +1,10 @@
use text_io::read;
use std::io::{self, Write};
use super::Solver;
use super::cell;
use super::Step;
use crate::Solver;
use crate::cell;
const CHAR_SET: &str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
@ -79,6 +82,11 @@ impl Solver {
Some(value) => {
if value <= self.size {
self.grid[row_index][column_index].collapse(&cell::CollapseOption::Set(value))?;
self.history.push(Step {
position: (row_index, column_index),
state_set: value,
num_allowed_states: 1,
});
}
}
None => {}
@ -94,11 +102,15 @@ impl Solver {
Ok(())
}
pub fn progress_bar(&self, number_cell_init: usize) {
let bar_size = (self.size + self.square_size - 1)*2 + 1;
let progress = self.history.len()*bar_size/(self.size*self.size - number_cell_init);
let to_do = bar_size - progress;
print!("\r[{}{}]", "#".repeat(progress), "-".repeat(to_do));
pub fn progress_bar(&self, progress: f32) {
let progress = progress.clamp(0.0, 1.0);
let bar_size = ((self.size + self.square_size - 1)*2 + 1).clamp(10, 100);
let done = (bar_size as f32 * progress).round() as usize;
let to_do = bar_size - done;
print!("\r[{}{}] ({:.2?}%)", "#".repeat(done), "-".repeat(to_do), progress * 100.0);
if let Err(e) = io::stdout().flush() {
eprintln!("Error flushing stdout: {}", e);
}
}
pub fn debug_mode(&mut self, debug_display: bool) {
@ -110,7 +122,7 @@ impl Solver {
}
pub fn debug(&self, text: &str) {
if self.debug_display {
if self.debug_display && self.solve_display {
println!("{}", text);
}
}

20
todo
View file

@ -1,9 +1,17 @@
more check for impossible
banchmark 2 (n = 80000)
# total time : 451.66µs (80.53% propagation, 18.73% forced collapse, 0.00% backtrack)
- 13 propagations (27.98µs), 8 forced collapse (10.58µs), 0 backtrack (0.00ns)
(0 errors)
benchmark 3 (n = 8000)
# total time : 9.94ms (62.15% propagation, 37.50% forced collapse, 0.12% backtrack)
- 78 propagations (79.17µs), 48 forced collapse (77.63µs), 0 backtrack (0.00ns)
(0 errors)
cargo run 4 --grid --debug --ask --norand --limit 220
--d-fc-g
benchmark 4 (n = 300)
# total time : 116.36ms (43.53% propagation, 56.03% forced collapse, 0.23% backtrack)
- 265 propagations (191.13µs), 183 forced collapse (356.23µs), 4 backtrack (66.91µs)
error :
when backtracking, always adding the state back is wrong
(maybe it was not there)
benchmark 5 (n = 100)
# total time : 886.94ms (31.81% propagation, 67.66% forced collapse, 0.43% backtrack)
- 743 propagations (379.77µs), 506 forced collapse (1.19ms), 27 backtrack (140.68µs)