use crate::sat::clause_storage::LiteralStorage;
use crate::sat::cnf::Cnf;
use crate::sat::literal::Literal;
use crate::sat::solver::Solutions;
use itertools::Itertools;
use std::fmt::Display;
use std::num::NonZeroI32;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Board(Vec<Vec<usize>>);
impl Display for Board {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for row in &self.0 {
writeln!(
f,
"{}",
row.iter()
.map(std::string::ToString::to_string)
.collect_vec()
.join(" ")
)?;
}
Ok(())
}
}
impl Board {
#[must_use]
pub const fn new(board: Vec<Vec<usize>>) -> Self {
Self(board)
}
}
impl From<Vec<Vec<usize>>> for Board {
fn from(board: Vec<Vec<usize>>) -> Self {
Self::new(board)
}
}
impl From<Board> for Vec<Vec<usize>> {
fn from(board: Board) -> Self {
board.0
}
}
impl From<&Board> for Vec<Vec<usize>> {
fn from(board: &Board) -> Self {
board.0.clone()
}
}
impl<const N: usize> From<&[&[usize; N]; N]> for Board {
fn from(board: &[&[usize; N]; N]) -> Self {
Self::new(board.iter().map(|r| r.to_vec()).collect())
}
}
#[allow(dead_code)]
pub const EXAMPLE_FOUR: [[usize; 4]; 4] = [[2, 3, 0, 0], [4, 0, 0, 1], [0, 1, 2, 0], [0, 0, 0, 0]];
#[allow(dead_code)]
pub const EXAMPLE_NINE: [[usize; 9]; 9] = [
[5, 3, 0, 0, 7, 0, 0, 0, 0],
[6, 0, 0, 1, 9, 5, 0, 0, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[0, 0, 0, 0, 8, 0, 0, 7, 9],
];
#[allow(dead_code)]
pub const EXAMPLE_SIXTEEN: [[usize; 16]; 16] = [
[0, 11, 0, 0, 0, 2, 3, 14, 0, 0, 9, 12, 0, 0, 0, 16],
[15, 12, 0, 0, 0, 11, 0, 1, 13, 10, 0, 0, 0, 0, 7, 2],
[0, 0, 10, 0, 0, 0, 0, 0, 16, 11, 0, 1, 6, 4, 12, 3],
[0, 16, 14, 1, 0, 4, 0, 6, 0, 3, 0, 15, 0, 8, 0, 0],
[1, 6, 5, 12, 0, 0, 11, 0, 0, 9, 8, 0, 0, 0, 0, 0],
[0, 0, 0, 7, 14, 1, 8, 0, 0, 15, 6, 0, 13, 5, 0, 4],
[4, 15, 8, 0, 9, 13, 0, 0, 0, 0, 7, 16, 3, 0, 0, 0],
[0, 9, 13, 0, 0, 0, 0, 15, 10, 0, 0, 0, 7, 6, 0, 11],
[14, 0, 6, 11, 0, 0, 0, 12, 7, 0, 0, 0, 0, 3, 13, 0],
[0, 0, 0, 5, 8, 14, 0, 0, 0, 0, 13, 11, 0, 1, 2, 6],
[13, 0, 16, 4, 0, 15, 5, 0, 0, 1, 12, 6, 8, 0, 0, 0],
[0, 0, 0, 0, 0, 16, 10, 0, 0, 8, 0, 0, 11, 9, 4, 5],
[0, 0, 11, 0, 1, 0, 14, 0, 5, 0, 3, 0, 15, 7, 16, 0],
[5, 13, 15, 3, 16, 0, 4, 7, 0, 0, 0, 0, 0, 2, 0, 0],
[16, 1, 0, 0, 0, 0, 12, 2, 14, 0, 15, 0, 0, 0, 3, 8],
[9, 0, 0, 0, 13, 5, 0, 0, 8, 6, 16, 0, 0, 0, 10, 0],
];
#[allow(dead_code)]
pub const EXAMPLE_TWENTY_FIVE: [[usize; 25]; 25] = [
[
0, 2, 0, 0, 0, 3, 14, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 13, 4, 24, 0, 7, 1, 0, 0,
],
[
0, 10, 17, 0, 0, 0, 6, 18, 0, 0, 22, 16, 0, 12, 0, 0, 0, 0, 1, 0, 0, 0, 13, 19, 0,
],
[
0, 15, 24, 13, 7, 0, 0, 0, 4, 0, 10, 0, 0, 3, 14, 0, 18, 0, 0, 0, 0, 22, 2, 6, 0,
],
[
0, 0, 1, 21, 0, 0, 15, 0, 22, 0, 0, 19, 13, 0, 0, 0, 8, 0, 0, 0, 0, 16, 18, 20, 0,
],
[
0, 5, 0, 0, 20, 7, 25, 19, 0, 0, 0, 21, 17, 18, 2, 10, 12, 22, 9, 15, 11, 0, 0, 0, 0,
],
[
11, 0, 0, 0, 22, 8, 0, 24, 7, 1, 5, 0, 0, 0, 13, 16, 17, 25, 23, 2, 4, 0, 6, 0, 19,
],
[
16, 9, 12, 0, 17, 0, 19, 22, 0, 0, 0, 0, 18, 21, 0, 0, 20, 6, 13, 0, 7, 0, 0, 23, 11,
],
[
0, 0, 6, 0, 21, 9, 16, 0, 3, 0, 0, 22, 20, 19, 0, 0, 0, 0, 15, 8, 25, 0, 0, 0, 0,
],
[
0, 0, 23, 5, 0, 2, 0, 0, 11, 17, 8, 0, 0, 0, 16, 12, 9, 0, 0, 21, 0, 3, 10, 0, 0,
],
[
0, 0, 0, 0, 0, 6, 0, 0, 12, 0, 9, 1, 25, 0, 3, 0, 11, 0, 0, 7, 0, 0, 21, 0, 0,
],
[
0, 0, 9, 0, 0, 23, 0, 5, 17, 4, 16, 0, 11, 0, 22, 18, 2, 0, 21, 13, 0, 0, 7, 0, 0,
],
[
4, 6, 0, 0, 5, 0, 0, 2, 0, 0, 0, 18, 21, 24, 0, 0, 19, 3, 0, 12, 23, 0, 0, 17, 0,
],
[
0, 0, 0, 12, 11, 0, 7, 3, 0, 24, 17, 20, 15, 13, 19, 1, 0, 5, 8, 0, 6, 9, 0, 0, 0,
],
[
0, 22, 0, 0, 14, 19, 0, 6, 16, 0, 0, 8, 9, 7, 0, 0, 0, 24, 0, 0, 3, 0, 0, 1, 18,
],
[
0, 0, 21, 0, 0, 25, 13, 0, 20, 8, 12, 0, 14, 0, 10, 9, 16, 15, 0, 6, 0, 0, 4, 0, 0,
],
[
0, 0, 25, 0, 0, 24, 0, 0, 18, 0, 4, 0, 3, 10, 5, 0, 1, 0, 0, 14, 0, 0, 0, 0, 0,
],
[
0, 0, 5, 3, 0, 17, 0, 0, 23, 7, 13, 0, 0, 0, 18, 19, 21, 0, 0, 22, 0, 11, 12, 0, 0,
],
[
0, 0, 0, 0, 18, 10, 8, 0, 0, 0, 0, 25, 23, 2, 0, 0, 5, 0, 16, 11, 9, 0, 3, 0, 0,
],
[
17, 20, 0, 0, 2, 0, 22, 16, 6, 0, 0, 7, 12, 0, 0, 0, 0, 9, 3, 0, 18, 0, 23, 24, 25,
],
[
6, 0, 4, 0, 16, 1, 11, 12, 25, 3, 19, 0, 0, 0, 21, 17, 23, 8, 0, 18, 2, 0, 0, 0, 14,
],
[
0, 0, 0, 0, 4, 14, 24, 11, 19, 23, 21, 17, 16, 8, 0, 0, 0, 1, 2, 9, 13, 0, 0, 5, 0,
],
[
0, 1, 14, 23, 0, 0, 0, 0, 9, 0, 0, 0, 19, 5, 0, 0, 24, 0, 12, 0, 0, 8, 17, 0, 0,
],
[
0, 16, 11, 8, 0, 0, 0, 0, 1, 0, 6, 4, 0, 0, 23, 0, 15, 0, 0, 0, 14, 12, 9, 10, 0,
],
[
0, 21, 3, 0, 0, 0, 17, 0, 0, 0, 0, 15, 0, 25, 20, 0, 0, 4, 10, 0, 0, 0, 16, 11, 0,
],
[
0, 0, 20, 2, 0, 16, 5, 8, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 19, 25, 0, 0, 0, 3, 0,
],
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Size {
Four = 4,
Nine = 9,
Sixteen = 16,
TwentyFive = 25,
}
impl TryFrom<usize> for Size {
type Error = ();
fn try_from(value: usize) -> Result<Self, Self::Error> {
match value {
4 => Ok(Self::Four),
9 => Ok(Self::Nine),
16 => Ok(Self::Sixteen),
25 => Ok(Self::TwentyFive),
_ => Err(()),
}
}
}
impl From<Size> for usize {
fn from(size: Size) -> Self {
match size {
Size::Four => 4,
Size::Nine => 9,
Size::Sixteen => 16,
Size::TwentyFive => 25,
}
}
}
impl Display for Size {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let size: usize = (*self).into();
write!(f, "{size}x{size}")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Sudoku {
pub board: Board,
pub size: Size,
}
impl Display for Sudoku {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Sudoku {{ size: {} }}", self.size)?;
write!(f, "{}", self.board)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Variable {
pub row: usize,
pub col: usize,
pub num: usize,
}
impl Variable {
#[must_use]
pub const fn new(row: usize, col: usize, num: usize) -> Self {
Self { row, col, num }
}
#[must_use]
pub const fn encode(&self, size: Size) -> usize {
let board_size = size as usize;
let row = self.row;
let col = self.col;
let num = self.num;
((row - 1) * board_size * board_size + (col - 1) * board_size + (num - 1)) + 1
}
}
impl Size {
#[must_use]
pub const fn block_size(self) -> usize {
match self {
Self::Four => 2,
Self::Nine => 3,
Self::Sixteen => 4,
Self::TwentyFive => 5,
}
}
}
fn generate_cell_clauses(size: usize) -> Vec<Vec<i32>> {
let mut clauses = vec![];
for row in 1..=size {
for col in 1..=size {
let clause = (1..=size)
.map(|num| {
i32::try_from(
Variable::new(row, col, num)
.encode(Size::try_from(size).expect("Invalid size")),
)
.expect("Invalid variable")
})
.collect();
clauses.push(clause);
}
}
clauses
}
fn generate_row_clauses(size: usize) -> Vec<Vec<i32>> {
let mut clauses = vec![];
for row in 1..=size {
for num in 1..=size {
for col1 in 1..=size {
for col2 in (col1 + 1)..=size {
if col1 > col2 {
continue;
}
let var1 = Variable::new(row, col1, num);
let var2 = Variable::new(row, col2, num);
clauses.push(vec![
-i32::try_from(var1.encode(Size::try_from(size).expect("Invalid size")))
.expect("Invalid variable"),
-i32::try_from(var2.encode(Size::try_from(size).expect("Invalid size")))
.expect("Invalid variable"),
]);
}
}
}
}
clauses
}
fn generate_col_clauses(size: usize) -> Vec<Vec<i32>> {
let mut clauses = vec![];
for col in 1..=size {
for num in 1..=size {
for row1 in 1..=size {
for row2 in (row1 + 1)..=size {
let var1 = Variable::new(row1, col, num);
let var2 = Variable::new(row2, col, num);
clauses.push(vec![
-i32::try_from(var1.encode(Size::try_from(size).expect("Invalid size")))
.expect("Invalid variable"),
-i32::try_from(var2.encode(Size::try_from(size).expect("Invalid size")))
.expect("Invalid variable"),
]);
}
}
}
}
clauses
}
fn generate_block_clauses(board_size: usize, block_size: usize) -> Vec<Vec<i32>> {
let mut clauses = Vec::new();
for n in 1..=board_size {
for br_start_idx in (0..board_size).step_by(block_size) {
for bc_start_idx in (0..board_size).step_by(block_size) {
let mut block_cells = Vec::new();
for r_offset in 0..block_size {
for c_offset in 0..block_size {
block_cells
.push((br_start_idx + r_offset + 1, bc_start_idx + c_offset + 1));
}
}
for i in 0..block_cells.len() {
for j in i + 1..block_cells.len() {
let (r1, c1) = block_cells[i];
let (r2, c2) = block_cells[j];
let var1 = Variable::new(r1, c1, n);
let var2 = Variable::new(r2, c2, n);
clauses.push(vec![
-i32::try_from(
var1.encode(Size::try_from(board_size).expect("Invalid size")),
)
.expect("Invalid variable"),
-i32::try_from(
var2.encode(Size::try_from(board_size).expect("Invalid size")),
)
.expect("Invalid variable"),
]);
}
}
}
}
}
clauses
}
fn generate_pre_filled_clauses(size: usize, board: &Board) -> Vec<Vec<i32>> {
let mut clauses = vec![];
for (r_idx, row_vec) in board.0.iter().enumerate() {
for (c_idx, &n_val) in row_vec.iter().enumerate() {
if n_val != 0 {
let var = Variable::new(r_idx + 1, c_idx + 1, n_val);
clauses.push(vec![
i32::try_from(var.encode(Size::try_from(size).expect("Invalid size")))
.expect("Invalid variable"),
]);
}
}
}
clauses
}
impl Sudoku {
pub fn new(board: Board) -> Result<Self, String> {
let size_val = board.0.len();
let size = Size::try_from(size_val).map_err(|()| {
format!("Invalid Sudoku size: {size_val}. Supported sizes are 4, 9, 16, or 25.",)
})?;
Ok(Self { board, size })
}
#[must_use]
pub fn decode(&self, solutions: &Solutions) -> Self {
let size_val: usize = self.size.into();
let mut board_vec = vec![vec![0; size_val]; size_val];
for row in 1..=size_val {
for col in 1..=size_val {
for num in 1..=size_val {
let var = Variable::new(row, col, num);
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let encoded_var = NonZeroI32::new(var.encode(self.size) as i32)
.expect("Encoded variable should not be zero");
if solutions.check(encoded_var) {
board_vec[row - 1][col - 1] = num;
}
}
}
}
Self {
board: Board::new(board_vec),
size: self.size,
}
}
#[must_use]
pub fn to_cnf<L: Literal, S: LiteralStorage<L>>(&self) -> Cnf<L, S> {
let size_val = self.size as usize;
let block_size_val = self.size.block_size();
let cell_clauses = generate_cell_clauses(size_val);
let row_clauses = generate_row_clauses(size_val);
let col_clauses = generate_col_clauses(size_val);
let block_clauses = generate_block_clauses(size_val, block_size_val);
let pre_filled_clauses = generate_pre_filled_clauses(size_val, &self.board);
let clauses: Vec<Vec<i32>> = cell_clauses
.into_iter()
.chain(row_clauses)
.chain(col_clauses)
.chain(block_clauses)
.chain(pre_filled_clauses)
.collect();
Cnf::new(clauses)
}
pub fn iter(&self) -> impl Iterator<Item = Vec<usize>> {
self.board.0.clone().into_iter()
}
pub fn from_string(sudoku_str: &str) -> Result<Self, String> {
parse_sudoku(sudoku_str)
}
pub fn from_file(file_path: &PathBuf) -> Result<Self, String> {
parse_sudoku_file(file_path)
}
}
impl TryFrom<Board> for Sudoku {
type Error = String;
fn try_from(board: Board) -> Result<Self, Self::Error> {
Self::new(board)
}
}
impl From<Sudoku> for Board {
fn from(sudoku: Sudoku) -> Self {
sudoku.board
}
}
pub fn parse_sudoku(sudoku: &str) -> Result<Sudoku, String> {
let lines = sudoku.lines().collect_vec();
let size = lines.len();
let mut board = vec![vec![0; size]; size];
for (i, line) in lines.iter().enumerate() {
if line.is_empty() {
continue;
}
for (j, c) in line.split_ascii_whitespace().enumerate() {
if j >= size {
return Err(format!(
"Invalid Sudoku: Too many numbers in line {}",
i + 1
));
}
if let Ok(num) = c.parse::<usize>() {
if num > size {
return Err(format!("Invalid Sudoku: Number {num} exceeds size {size}"));
}
board[i][j] = num;
} else {
return Err(format!("Invalid Sudoku: Invalid character {c}"));
}
}
}
Sudoku::new(Board::new(board))
}
pub fn parse_sudoku_file(file_path: &PathBuf) -> Result<Sudoku, String> {
let content = std::fs::read_to_string(file_path).map_err(|e| e.to_string())?;
parse_sudoku(&content)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn board_new_and_display() {
let data = vec![vec![1, 0], vec![0, 2]];
let board = Board::new(data.clone());
assert_eq!(board.0, data);
assert_eq!(format!("{board}"), "1 0\n0 2\n");
}
#[test]
fn board_from_vec_vec() {
let data = vec![vec![1], vec![2]];
let board: Board = data.clone().into();
assert_eq!(board.0, data);
}
#[test]
fn vec_vec_from_board() {
let data = vec![vec![1], vec![2]];
let board = Board::new(data.clone());
let converted_data: Vec<Vec<usize>> = board.into();
assert_eq!(converted_data, data);
}
#[test]
fn vec_vec_from_ref_board() {
let data = vec![vec![1], vec![2]];
let board = Board::new(data.clone());
let converted_data: Vec<Vec<usize>> = (&board).into();
assert_eq!(converted_data, data);
}
#[test]
fn board_from_array_of_refs() {
let r1: [usize; 2] = [1, 0];
let r2: [usize; 2] = [0, 2];
let board_data: [&[usize; 2]; 2] = [&r1, &r2];
let board = Board::from(&board_data);
assert_eq!(board, Board::new(vec![vec![1, 0], vec![0, 2]]));
}
#[test]
fn size_try_from_usize() {
assert_eq!(Size::try_from(4), Ok(Size::Four));
assert_eq!(Size::try_from(9), Ok(Size::Nine));
assert_eq!(Size::try_from(16), Ok(Size::Sixteen));
assert_eq!(Size::try_from(25), Ok(Size::TwentyFive));
assert_eq!(Size::try_from(1), Err(()));
assert_eq!(Size::try_from(0), Err(()));
}
#[test]
fn usize_from_size() {
assert_eq!(usize::from(Size::Four), 4);
assert_eq!(usize::from(Size::Nine), 9);
assert_eq!(usize::from(Size::Sixteen), 16);
assert_eq!(usize::from(Size::TwentyFive), 25);
}
#[test]
fn size_display() {
assert_eq!(format!("{}", Size::Four), "4x4");
assert_eq!(format!("{}", Size::Nine), "9x9");
}
#[test]
fn size_block_size() {
assert_eq!(Size::Four.block_size(), 2);
assert_eq!(Size::Nine.block_size(), 3);
assert_eq!(Size::Sixteen.block_size(), 4);
assert_eq!(Size::TwentyFive.block_size(), 5);
}
#[test]
fn variable_new_and_encode() {
let var = Variable::new(1, 1, 1);
assert_eq!(
var,
Variable {
row: 1,
col: 1,
num: 1
}
);
assert_eq!(Variable::new(1, 1, 1).encode(Size::Four), 1); assert_eq!(Variable::new(1, 1, 4).encode(Size::Four), 4); assert_eq!(Variable::new(1, 2, 1).encode(Size::Four), 5); assert_eq!(Variable::new(2, 1, 1).encode(Size::Four), 17); assert_eq!(Variable::new(4, 4, 4).encode(Size::Four), 64);
assert_eq!(Variable::new(1, 1, 1).encode(Size::Nine), 1);
assert_eq!(Variable::new(9, 9, 9).encode(Size::Nine), 729); }
#[test]
fn sudoku_new() {
let board_data = vec![vec![0; 4]; 4];
let board = Board::new(board_data);
let sudoku = Sudoku::new(board.clone());
assert!(sudoku.is_ok());
let sudoku = sudoku.unwrap();
assert_eq!(sudoku.board, board);
assert_eq!(sudoku.size, Size::Four);
}
#[test]
#[should_panic(expected = "Invalid size for Sudoku board")]
fn sudoku_new_invalid_size() {
let board_data = vec![vec![0; 5]; 5];
let board = Board::new(board_data);
let _ = Sudoku::new(board);
}
#[test]
fn sudoku_display() {
let board_data = vec![
vec![1, 0, 0, 0],
vec![0, 2, 0, 0],
vec![0, 0, 3, 0],
vec![0, 0, 0, 4],
];
let sudoku = Sudoku::new(Board::new(board_data));
assert!(sudoku.is_ok());
let sudoku = sudoku.unwrap();
let expected_display = "Sudoku { size: 4x4 }\n1 0 0 0\n0 2 0 0\n0 0 3 0\n0 0 0 4\n";
assert_eq!(format!("{sudoku}"), expected_display);
}
#[test]
fn sudoku_iter() {
let sudoku_board_data = vec![
vec![1, 2, 0, 0],
vec![3, 4, 0, 0],
vec![0, 0, 0, 0],
vec![0, 0, 0, 0],
];
let sudoku = Sudoku::try_from(Board::from(sudoku_board_data));
assert!(sudoku.is_ok());
let sudoku = sudoku.unwrap();
let mut iter = sudoku.iter();
assert_eq!(iter.next(), Some(vec![1, 2, 0, 0]));
assert_eq!(iter.next(), Some(vec![3, 4, 0, 0]));
assert_eq!(iter.next(), Some(vec![0, 0, 0, 0]));
assert_eq!(iter.next(), Some(vec![0, 0, 0, 0]));
assert_eq!(iter.next(), None);
}
#[test]
fn parse_sudoku_valid_4x4() {
let s = "1 0 3 0\n0 2 0 0\n0 0 0 4\n0 0 1 0";
let sudoku = parse_sudoku(s).unwrap();
assert_eq!(sudoku.size, Size::Four);
let expected_board = Board::new(vec![
vec![1, 0, 3, 0],
vec![0, 2, 0, 0],
vec![0, 0, 0, 4],
vec![0, 0, 1, 0],
]);
assert_eq!(sudoku.board, expected_board);
}
#[test]
#[should_panic(expected = "Invalid size for Sudoku board")]
fn parse_sudoku_invalid_size_panic() {
let s = "1 0\n0 1";
parse_sudoku(s).unwrap();
}
#[test]
fn parse_sudoku_invalid_char() {
let s = "1 0 X 0\n0 2 0 0\n0 0 0 4\n0 0 1 0";
assert!(parse_sudoku(s).is_err());
assert_eq!(
parse_sudoku(s).unwrap_err(),
"Invalid Sudoku: Invalid character X"
);
}
#[test]
fn parse_sudoku_number_too_large() {
let s = "1 0 5 0\n0 2 0 0\n0 0 0 4\n0 0 1 0"; let err = parse_sudoku(s).unwrap_err();
assert_eq!(err, "Invalid Sudoku: Number 5 exceeds size 4");
}
#[test]
fn parse_sudoku_too_many_numbers_in_line() {
let s = "1 0 0 0 5\n0 2 0 0\n0 0 0 4\n0 0 1 0";
match parse_sudoku(s) {
Ok(_) => panic!("Should have failed or panicked."),
Err(e) => {
assert_eq!(e, "Invalid Sudoku: Too many numbers in line 1");
}
}
}
#[test]
fn parse_sudoku_too_few_numbers_in_line() {
let s = "1 0\n0 2 0 0\n0 0 0 4\n0 0 1 0";
let sudoku = parse_sudoku(s).unwrap();
let expected_board = Board::new(vec![
vec![1, 0, 0, 0], vec![0, 2, 0, 0],
vec![0, 0, 0, 4],
vec![0, 0, 1, 0],
]);
assert_eq!(sudoku.board, expected_board);
}
#[test]
fn parse_sudoku_empty_line_behavior() {
let s = "1 0 3 0\n0 2 0 0\n\n0 0 0 4\n0 0 1 0";
assert!(
std::panic::catch_unwind(|| parse_sudoku(s)).is_err(),
"Sudoku::new should panic for size 5"
);
}
#[test]
fn sudoku_decode_basic() {
let initial_board_data = vec![vec![0; 4]; 4];
let sudoku = Sudoku::new(Board::new(initial_board_data));
assert!(sudoku.is_ok());
let sudoku = sudoku.unwrap();
let mut solution_vars = Vec::new();
let solution_grid: [[usize; 4]; 4] =
[[1, 2, 3, 4], [3, 4, 1, 2], [2, 1, 4, 3], [4, 3, 2, 1]];
for (r, row) in solution_grid.iter().enumerate() {
for (c, &num) in row.iter().enumerate() {
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
solution_vars.push(Variable::new(r + 1, c + 1, num).encode(Size::Four) as i32);
}
}
let solutions = Solutions::new(&solution_vars);
let decoded_sudoku = sudoku.decode(&solutions);
let expected_board_data: Vec<Vec<usize>> =
solution_grid.iter().map(|row| row.to_vec()).collect();
assert_eq!(decoded_sudoku.board.0, expected_board_data);
assert_eq!(decoded_sudoku.size, Size::Four);
}
#[test]
fn test_generate_cell_clauses_content() {
let cell_clauses = generate_cell_clauses(4);
let clause_for_cell_1_1 = vec![1, 2, 3, 4];
assert!(cell_clauses.contains(&clause_for_cell_1_1));
assert_eq!(cell_clauses.len(), 4 * 4);
}
#[test]
fn test_generate_row_clauses_content() {
let row_clauses = generate_row_clauses(4);
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let var111 = Variable::new(1, 1, 1).encode(Size::Four) as i32;
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let var121 = Variable::new(1, 2, 1).encode(Size::Four) as i32;
let clause_example = vec![-var111, -var121]; assert!(row_clauses.contains(&clause_example));
assert_eq!(row_clauses.len(), 96);
}
#[test]
fn test_generate_pre_filled_clauses() {
let board_data = vec![
vec![1, 0, 0, 0],
vec![0, 2, 0, 0],
vec![0, 0, 0, 0],
vec![0, 0, 0, 0],
];
let board = Board::new(board_data);
let pre_filled = generate_pre_filled_clauses(4, &board);
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let var111 = Variable::new(1, 1, 1).encode(Size::Four) as i32;
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let var222 = Variable::new(2, 2, 2).encode(Size::Four) as i32;
let expected_clauses: HashSet<Vec<i32>> =
[vec![var111], vec![var222]].iter().cloned().collect();
let actual_clauses: HashSet<Vec<i32>> = pre_filled.iter().cloned().collect();
assert_eq!(actual_clauses, expected_clauses);
assert_eq!(pre_filled.len(), 2);
}
}