use crate::Sudoku;
use crate::constraint::Constraint;
use crate::error::{SudokuError, SudokuResult};
use crate::solver::{BacktrackingSolver, Solution, Solver};
use rand::Rng;
use rand::rngs::ThreadRng;
pub struct Generator<R: Rng> {
rng: R
}
impl Generator<ThreadRng> {
pub fn new_default() -> Generator<ThreadRng> {
Generator::new(rand::thread_rng())
}
}
fn shuffle<T>(rng: &mut impl Rng, values: impl Iterator<Item = T>) -> Vec<T> {
let mut vec: Vec<T> = values.collect();
let len = vec.len();
for i in 0..(len - 1) {
let j = rng.gen_range(i..len);
vec.swap(i, j);
}
vec
}
impl<R: Rng> Generator<R> {
pub fn new(rng: R) -> Generator<R> {
Generator {
rng
}
}
fn generate_rec<C: Constraint + Clone>(&mut self, sudoku: &mut Sudoku<C>,
column: usize, row: usize) -> bool {
let size = sudoku.grid().size();
if row == size {
return true;
}
let next_column = (column + 1) % size;
let next_row =
if next_column == 0 { row + 1 } else { row };
for number in shuffle(&mut self.rng, 1..=size) {
if sudoku.is_valid_number(column, row, number).unwrap() {
sudoku.grid_mut().set_cell(column, row, number).unwrap();
if self.generate_rec(sudoku, next_column, next_row) {
return true;
}
sudoku.grid_mut().clear_cell(column, row).unwrap();
}
}
false
}
pub fn generate<C: Constraint + Clone>(&mut self, block_width: usize,
block_height: usize, constraint: C) -> SudokuResult<Sudoku<C>> {
let mut sudoku =
Sudoku::new_empty(block_width, block_height, constraint)?;
if self.generate_rec(&mut sudoku, 0, 0) {
Ok(sudoku)
}
else {
Err(SudokuError::UnsatisfiableConstraint)
}
}
}
pub struct Reducer<S: Solver, R: Rng> {
solver: S,
rng: R
}
impl Reducer<BacktrackingSolver, ThreadRng> {
pub fn new_default() -> Reducer<BacktrackingSolver, ThreadRng> {
Reducer::new(BacktrackingSolver, rand::thread_rng())
}
}
impl<S: Solver, R: Rng> Reducer<S, R> {
pub fn new(solver: S, rng: R) -> Reducer<S, R> {
Reducer {
solver,
rng
}
}
pub fn reduce<C: Constraint + Clone>(&mut self, sudoku: &mut Sudoku<C>) {
let size = sudoku.grid().size();
let coords= (0..size)
.flat_map(|column| (0..size).map(move |row| (column, row)));
for (column, row) in shuffle(&mut self.rng, coords) {
if let Some(number) =
sudoku.grid().get_cell(column, row).unwrap() {
sudoku.grid_mut().clear_cell(column, row).unwrap();
if let Solution::Unique(_) = self.solver.solve(sudoku) { }
else {
sudoku.grid_mut().set_cell(column, row, number).unwrap();
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constraint::DefaultConstraint;
const DEFAULT_BLOCK_WIDTH: usize = 3;
const DEFAULT_BLOCK_HEIGHT: usize = 3;
fn generate_default() -> Sudoku<DefaultConstraint> {
let mut generator = Generator::new_default();
generator.generate(DEFAULT_BLOCK_WIDTH, DEFAULT_BLOCK_HEIGHT,
DefaultConstraint).unwrap()
}
fn reduce_default() -> Sudoku<DefaultConstraint> {
let mut sudoku = generate_default();
let mut reducer = Reducer::new_default();
reducer.reduce(&mut sudoku);
sudoku
}
#[test]
fn shuffling_uniformly_distributed() {
let mut counts = [0; 6];
let mut rng = rand::thread_rng();
for _ in 0..18000 {
let result = shuffle(&mut rng, 1..=3);
if result == vec![1, 2, 3] {
counts[0] += 1;
}
else if result == vec![1, 3, 2] {
counts[1] += 1;
}
else if result == vec![2, 1, 3] {
counts[2] += 1;
}
else if result == vec![2, 3, 1] {
counts[3] += 1;
}
else if result == vec![3, 1, 2] {
counts[4] += 1;
}
else if result == vec![3, 2, 1] {
counts[5] += 1;
}
}
for count in counts.iter() {
assert!(*count >= 2600 && *count <= 3400,
"Count is not in range [2600, 3400].");
}
}
#[test]
fn generated_sudoku_valid() {
let sudoku = generate_default();
assert!(sudoku.is_valid(), "Generated Sudoku not valid.");
}
#[test]
fn generated_sudoku_full() {
let sudoku = generate_default();
let size = DEFAULT_BLOCK_WIDTH * DEFAULT_BLOCK_HEIGHT;
assert_eq!(size * size, sudoku.grid().count_clues(),
"Generated Sudoku is not full.");
}
#[test]
fn reduced_sudoku_valid_and_not_full() {
let sudoku = reduce_default();
let size = DEFAULT_BLOCK_WIDTH * DEFAULT_BLOCK_HEIGHT;
assert!(sudoku.is_valid(), "Reduced Sudoku not valid.");
assert!(sudoku.grid().count_clues() <= size * (size - 1),
"Reduced Sudoku has too many clues.");
}
#[test]
fn reduced_sudoku_uniquely_solveable() {
let sudoku = reduce_default();
let solver = BacktrackingSolver;
let solution = solver.solve(&sudoku);
if let Solution::Unique(_) = solution { }
else {
panic!("Reduced Sudoku not uniquely solveable.")
}
}
struct TopLeftSolver;
impl Solver for TopLeftSolver {
fn solve(&self, sudoku: &Sudoku<impl Constraint + Clone>) -> Solution {
let size = sudoku.grid().size();
let cells = size * size;
let clues = sudoku.grid().count_clues();
if clues == cells {
return Solution::Unique(sudoku.grid().clone());
}
else if clues < cells - 1 {
return Solution::Ambiguous;
}
if let Some(_) = sudoku.grid().get_cell(0, 0).unwrap() {
Solution::Ambiguous
}
else {
let mut number = None;
for i in 1..=size {
if sudoku.is_valid_number(0, 0, i).unwrap() {
if number == None {
number = Some(i);
}
else {
return Solution::Ambiguous;
}
}
}
if let Some(number) = number {
let mut result_grid = sudoku.grid().clone();
result_grid.set_cell(0, 0, number).unwrap();
Solution::Unique(result_grid)
}
else {
Solution::Impossible
}
}
}
}
#[test]
fn reduced_sudoku_solveable_by_solver() {
let mut sudoku = generate_default();
let mut reducer = Reducer::new(TopLeftSolver, rand::thread_rng());
reducer.reduce(&mut sudoku);
let size = DEFAULT_BLOCK_WIDTH * DEFAULT_BLOCK_HEIGHT;
assert_eq!(size * size - 1, sudoku.grid().count_clues(),
"Reduced Sudoku missing too many clues or not reduced at all.");
assert_eq!(None, sudoku.grid().get_cell(0, 0).unwrap(),
"Reduced Sudoku missing wrong clue.");
}
}