Skip to content

Commit

Permalink
major refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinschweikert committed Feb 18, 2024
1 parent 5035aad commit 5be7011
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 112 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "sudoku-solver"
name = "sudoku_solver"
version = "0.1.0"
edition = "2021"

Expand Down
37 changes: 13 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
# Rust Sudoku Solver

Reference: https://gist.github.com/syphh/62e6140361feb2d7196f2cb050c987b3
[Reference](https://gist.github.com/syphh/62e6140361feb2d7196f2cb050c987b3)

```rust
fn main() {
let mut grid: Grid = [
[0, 0, 0, 4, 0, 6, 0, 3, 5],
[0, 6, 0, 0, 0, 0, 0, 0, 0],
[5, 0, 2, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 4, 1, 0],
[3, 0, 0, 0, 9, 0, 0, 7, 0],
[4, 9, 0, 0, 0, 0, 8, 2, 0],
[0, 0, 0, 2, 0, 9, 0, 0, 0],
[0, 0, 8, 7, 1, 0, 0, 0, 0],
[0, 7, 0, 0, 0, 0, 3, 0, 1],
];
## Usage

solve(&mut grid, 0, 0);
println!("{}", GridWrapper(grid));
}
```rust
cargo run ...4.6.35.6.......5.23...........41.3...9..7.49....82....2.9.....871.....7....3.2
```

Result:

```shell
8 1 9 4 7 6 2 3 5
7 6 3 9 2 5 1 8 4
5 4 2 3 8 1 9 6 7
9 1 7 4 8 6 2 3 5
8 6 3 9 2 5 1 4 7
5 4 2 3 7 1 6 9 8

6 8 7 5 3 2 4 1 9
7 8 6 5 3 2 4 1 9
3 2 1 8 9 4 5 7 6
4 9 5 1 6 7 8 2 3

1 3 6 2 4 9 7 5 8
9 5 8 7 1 3 6 4 2
2 7 4 6 5 8 3 9 1
6 3 4 2 5 9 7 8 1
2 5 8 7 1 3 9 6 4
1 7 9 6 4 8 3 5 2
```
113 changes: 113 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
pub mod sudoku {
use std::fmt;
use std::str::FromStr;

#[derive(Debug, PartialEq)]
pub struct Sudoku([[usize; 9]; 9]);

#[derive(Debug, PartialEq, Eq)]
pub struct ParseSudokuError(String);

#[derive(Debug)]
pub struct SolveError;

impl Sudoku {
pub fn new(data: [[usize; 9]; 9]) -> Self {
Sudoku(data)
}

/// Solves the given sudoku
pub fn solve(&mut self) -> Result<(), SolveError> {
solve_recursive(self, 0, 0)
}
}

/// Checks if a given number `k` is valid in the row `r` and column `c`
pub fn is_valid(sudoku: &Sudoku, r: usize, c: usize, k: usize) -> bool {
let grid = sudoku.0;
let not_in_row = !grid[r].contains(&k);
let not_in_column = (0..9).all(|i| grid[i][c] != k);
let not_in_box =
(0..3).all(|i| (0..3).all(|j| grid[(r / 3) * 3 + i][(c / 3) * 3 + j] != k));

not_in_row && not_in_column && not_in_box
}

fn solve_recursive(sudoku: &mut Sudoku, r: usize, c: usize) -> Result<(), SolveError> {
if r == 9 {
Ok(())
} else if c == 9 {
solve_recursive(sudoku, r + 1, 0)
} else if sudoku.0[r][c] != 0 {
solve_recursive(sudoku, r, c + 1)
} else {
for k in 1..10 {
if is_valid(&*sudoku, r, c, k) {
sudoku.0[r][c] = k;
if solve_recursive(sudoku, r, c + 1).is_ok() {
return Ok(());
}
sudoku.0[r][c] = 0;
}
}
Err(SolveError)
}
}

impl fmt::Display for Sudoku {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (r, row) in self.0.iter().enumerate() {
for (c, col) in row.iter().enumerate() {
write!(
f,
"{} {}",
col,
if (c + 1) % 3 == 0 && c != 8 { " " } else { "" }
)?;
}
writeln!(f)?;
if (r + 1) % 3 == 0 && r != 8 {
writeln!(f)?;
}
}
Ok(())
}
}

impl FromStr for Sudoku {
type Err = ParseSudokuError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let string = s.to_string();
let input = string.trim();
if input.len() != 9 * 9 {
Err(ParseSudokuError(format!(
"Grid incomplete. {} from {} cells found",
input.len(),
9 * 9
)))
} else {
let mut grid = [[0; 9]; 9]; // Initialize a 9x9 grid
let mut char_iter = input.chars();

for r in 0..9 {
for c in 0..9 {
let char = char_iter.next().unwrap(); // We already checked input length, so this is safe
grid[r][c] = match char {
'.' | '0' => 0,
'1'..='9' => char.to_digit(10).unwrap() as usize, // Convert character to digit
_ => {
return Err(ParseSudokuError(
"Input must only contain numbers between 1 and 9 or a '.'"
.to_string(),
))
}
};
}
}

Ok(Sudoku(grid))
}
}
}
}
94 changes: 8 additions & 86 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,90 +1,12 @@
use std::fmt;

type Grid = [[usize; 9]; 9];
struct GridWrapper(Grid);

impl fmt::Display for GridWrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (r, row) in self.0.iter().enumerate() {
for (c, col) in row.iter().enumerate() {
write!(
f,
"{} {}",
col,
if (c + 1) % 3 == 0 && c != 8 { " " } else { "" }
)?;
}
writeln!(f)?;
if (r + 1) % 3 == 0 && r != 8 {
writeln!(f)?;
}
}
Ok(())
}
}
use std::env;
use std::str::FromStr;
use sudoku_solver::sudoku::Sudoku;

fn main() {
let mut grid: Grid = [
[0, 0, 0, 4, 0, 6, 0, 3, 5],
[0, 6, 0, 0, 0, 0, 0, 0, 0],
[5, 0, 2, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 4, 1, 0],
[3, 0, 0, 0, 9, 0, 0, 7, 0],
[4, 9, 0, 0, 0, 0, 8, 2, 0],
[0, 0, 0, 2, 0, 9, 0, 0, 0],
[0, 0, 8, 7, 1, 0, 0, 0, 0],
[0, 7, 0, 0, 0, 0, 3, 0, 1],
];

solve(&mut grid, 0, 0);
println!("{}", GridWrapper(grid));
}

fn is_valid(grid: &mut Grid, r: usize, c: usize, k: usize) -> bool {
let not_in_row = !grid[r].contains(&k);
let not_in_column = (0..9).all(|i| grid[i][c] != k);
let not_in_box = (0..3).all(|i| (0..3).all(|j| grid[(r / 3) * 3 + i][(c / 3) * 3 + j] != k));

not_in_row && not_in_column && not_in_box
}

fn solve(grid: &mut Grid, r: usize, c: usize) -> bool {
if r == 9 {
true
} else if c == 9 {
solve(grid, r + 1, 0)
} else if grid[r][c] != 0 {
solve(grid, r, c + 1)
} else {
for k in 1..10 {
if is_valid(grid, r, c, k) {
grid[r][c] = k;
if solve(grid, r, c + 1) {
return true;
}
grid[r][c] = 0;
}
}
false
}
}

#[test]
fn is_valid_test() {
let mut grid: Grid = [
[0, 0, 4, 0, 5, 0, 0, 0, 0],
[9, 5, 0, 7, 3, 4, 6, 0, 0],
[0, 0, 3, 0, 2, 1, 0, 4, 9],
[0, 3, 5, 0, 9, 0, 4, 8, 0],
[0, 9, 0, 0, 0, 0, 0, 3, 0],
[0, 7, 6, 0, 1, 0, 9, 2, 0],
[3, 1, 0, 9, 7, 0, 2, 0, 0],
[0, 0, 9, 1, 8, 2, 0, 0, 3],
[0, 0, 0, 0, 6, 0, 1, 0, 0],
];
let args: Vec<String> = env::args().collect();
let input = &args[1];

assert!(is_valid(&mut grid, 0, 0, 1));
assert!(!is_valid(&mut grid, 0, 0, 4));
assert!(!is_valid(&mut grid, 0, 0, 3));
assert!(!is_valid(&mut grid, 0, 0, 5));
let mut sudoku = Sudoku::from_str(input).expect("No valid input found");
sudoku.solve().expect("Grid must be solvable");
println!("{}", sudoku);
}
72 changes: 72 additions & 0 deletions tests/sudoku_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::str::FromStr;
use sudoku_solver::sudoku::{is_valid, Sudoku};

#[test]
fn is_valid_test() {
let sudoku = Sudoku::new([
[0, 0, 4, 0, 5, 0, 0, 0, 0],
[9, 5, 0, 7, 3, 4, 6, 0, 0],
[0, 0, 3, 0, 2, 1, 0, 4, 9],
[0, 3, 5, 0, 9, 0, 4, 8, 0],
[0, 9, 0, 0, 0, 0, 0, 3, 0],
[0, 7, 6, 0, 1, 0, 9, 2, 0],
[3, 1, 0, 9, 7, 0, 2, 0, 0],
[0, 0, 9, 1, 8, 2, 0, 0, 3],
[0, 0, 0, 0, 6, 0, 1, 0, 0],
]);

assert!(is_valid(&sudoku, 0, 0, 1));
assert!(!is_valid(&sudoku, 0, 0, 4));
assert!(!is_valid(&sudoku, 0, 0, 3));
assert!(!is_valid(&sudoku, 0, 0, 5));
}

#[test]
fn solve_test() {
let mut sudoku = Sudoku::new([
[0, 0, 0, 4, 0, 6, 0, 3, 5],
[0, 6, 0, 0, 0, 0, 0, 0, 0],
[5, 0, 2, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 4, 1, 0],
[3, 0, 0, 0, 9, 0, 0, 7, 0],
[4, 9, 0, 0, 0, 0, 8, 2, 0],
[0, 0, 0, 2, 0, 9, 0, 0, 0],
[0, 0, 8, 7, 1, 0, 0, 0, 0],
[0, 7, 0, 0, 0, 0, 3, 0, 1],
]);

let solution = Sudoku::new([
[8, 1, 9, 4, 7, 6, 2, 3, 5],
[7, 6, 3, 9, 2, 5, 1, 8, 4],
[5, 4, 2, 3, 8, 1, 9, 6, 7],
[6, 8, 7, 5, 3, 2, 4, 1, 9],
[3, 2, 1, 8, 9, 4, 5, 7, 6],
[4, 9, 5, 1, 6, 7, 8, 2, 3],
[1, 3, 6, 2, 4, 9, 7, 5, 8],
[9, 5, 8, 7, 1, 3, 6, 4, 2],
[2, 7, 4, 6, 5, 8, 3, 9, 1],
]);

sudoku.solve().unwrap();
assert_eq!(sudoku, solution);
}

#[test]
fn from_string() {
let input = "...4.6.35.6.......5.23...........41.3...9..7.49....82....2.9.....871.....7....3.1";
let parsed = Sudoku::from_str(input).unwrap();

let solution = Sudoku::new([
[0, 0, 0, 4, 0, 6, 0, 3, 5],
[0, 6, 0, 0, 0, 0, 0, 0, 0],
[5, 0, 2, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 4, 1, 0],
[3, 0, 0, 0, 9, 0, 0, 7, 0],
[4, 9, 0, 0, 0, 0, 8, 2, 0],
[0, 0, 0, 2, 0, 9, 0, 0, 0],
[0, 0, 8, 7, 1, 0, 0, 0, 0],
[0, 7, 0, 0, 0, 0, 3, 0, 1],
]);

assert_eq!(parsed, solution);
}

0 comments on commit 5be7011

Please sign in to comment.