Skip to content

Commit 672fb18

Browse files
committed
fix bug that caused solver to hang indefinitely when given an illegal puzzle
1 parent ba09610 commit 672fb18

4 files changed

Lines changed: 72 additions & 5 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "lib_sudoku"
7-
version = "1.6"
7+
version = "2.0.0"
88
requires-python = ">=3.8"
99
description = "A blazing fast sudoku library buit in rust"
1010
readme = "README.md"

src/libraries/puzzle_solver.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,66 @@ pub(crate) struct Puzzle {
77
pub current_pos: Vec<i8>,
88
pub solved: bool,
99
}
10+
11+
fn no_repeats(items: Vec<u8>) -> bool {
12+
let mut seen: [bool;10] = [false;10];
13+
for item in items.iter() {
14+
if *item == 0 {
15+
continue;
16+
}
17+
if seen[*item as usize] {
18+
return false;
19+
}
20+
seen[*item as usize] = true;
21+
}
22+
true
23+
}
24+
25+
#[pyfunction]
26+
fn is_valid(puzzle: Vec<u8>) -> PyResult<bool> {
27+
if puzzle.len() != 81 {
28+
return Err(pyo3::exceptions::PyValueError::new_err("A puzzle must have a length of 81!"));
29+
}
30+
31+
for i in 0..9 { //check rows
32+
if !no_repeats(Vec::from(&puzzle[i * 9..(i + 1) * 9])) {
33+
return Ok(false);
34+
}
35+
36+
if !no_repeats(puzzle.iter().skip(i) //check columns
37+
.step_by(9)
38+
.copied()
39+
.collect::<Vec<u8>>()) {
40+
return Ok(false);
41+
}
42+
43+
}
44+
45+
let mut row = 0;
46+
let mut col = 0;
47+
48+
loop {
49+
let mut square_to_check: Vec<u8> = vec![0; 9];
50+
for r in row..row + 3 {
51+
for c in col..col + 3 {
52+
square_to_check.push(puzzle[r * 9 + c])
53+
}
54+
}
55+
if !no_repeats(square_to_check.clone()) {
56+
return Ok(false);
57+
}
58+
if row == 6 {
59+
if col == 6 {
60+
break;
61+
}
62+
row = 0;
63+
col += 3;
64+
}
65+
row += 3;
66+
}
67+
Ok(true)
68+
}
69+
1070
pub(crate) fn get_possibilities(puzz: &Vec<u8>, pos: u8) -> Vec<u8> {
1171
let mut possibilities: Vec<u8> = Vec::new();
1272
let mut seen: [bool; 10] = [false; 10];
@@ -104,20 +164,24 @@ pub(crate) fn get_possibilities_as_array(puzz: &Vec<u8>, pos: usize) -> [bool; 1
104164
#[pyfunction]
105165
pub fn solve(puzz: Vec<u8>) -> PyResult<Vec<u8>> {
106166
if puzz.len() != 81 {
107-
return Err(pyo3::exceptions::PyValueError::new_err("A puzzle must have a length of 81!"));
167+
return Err(pyo3::exceptions::PyValueError::new_err("Your puzzle must have a length of 81!"));
108168
}
109169

110170
let mut p = solver_prep(puzz);
111171
if p.solved {
112172
return Ok(p.puzz);
113173
}
114174

175+
if !is_valid(p.puzz.clone())? {
176+
return Err(pyo3::exceptions::PyValueError::new_err("The puzzle is illegal!"));
177+
}
178+
115179
let mut position: i8 = 0;
116180
let max_pos = p.blank_positions.len();
117181
let mut progressed_forward = true;
118182
loop {
119183
if position < 0 {
120-
return Err(pyo3::exceptions::PyValueError::new_err(format!("The following puzzle is unsolvable!\n{:?}", p.puzz)));
184+
return Err(pyo3::exceptions::PyValueError::new_err("Your puzzle is unsolvable!"));
121185
} else if position == max_pos as i8 {
122186
p.solved = true;
123187
return Ok(p.puzz);
@@ -160,3 +224,4 @@ pub fn solve(puzz: Vec<u8>) -> PyResult<Vec<u8>> {
160224
pub async fn async_solve(puzz: Vec<u8>) -> PyResult<Vec<u8>> {
161225
solve(puzz)
162226
}
227+

src/libraries/speedtest.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub fn async_speedtest(puzzle_reader: &puzzle_reader::PuzzleReader, verbose: boo
6464

6565
#[pyfunction]
6666
#[pyo3(signature = (puzzle_reader, verbose = false))]
67-
pub fn synchronous_speedtest(puzzle_reader: &puzzle_reader::PuzzleReader, verbose: bool) {
67+
pub fn synchronous_speedtest(puzzle_reader: &puzzle_reader::PuzzleReader, verbose: bool) -> pyo3::PyResult<()> {
6868
println!("---------------\nStarting Synchronous Speedtest\n---------------");
6969
let mut solved_puzzles = Vec::new();
7070
let start_solve = std::time::Instant::now();
@@ -81,7 +81,7 @@ pub fn synchronous_speedtest(puzzle_reader: &puzzle_reader::PuzzleReader, verbos
8181
Ok(puzz) => {
8282
pc_solved = puzz.clone();
8383
},
84-
Err(_e) => {println!("Found an unsolvable puzzle at line {}", i + 2); break;},
84+
Err(_e) => return Err(pyo3::exceptions::PyValueError::new_err("The entered puzzle is invalid!"))
8585

8686
}
8787
if verbose {
@@ -104,4 +104,5 @@ pub fn synchronous_speedtest(puzzle_reader: &puzzle_reader::PuzzleReader, verbos
104104
} else {
105105
println!("Validated {} puzzles in {:?}", puzzle_reader.size, start_validate.elapsed());
106106
}
107+
Ok(())
107108
}

testing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def tests():
88
)
99
sudoku.async_speedtest(reader)
1010
sudoku.synchronous_speedtest(reader)
11+
#sudoku.print_puzz(sudoku.solve([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1]))
1112

1213
num_hints = 24
1314
start_gen = time.time()

0 commit comments

Comments
 (0)