Initial implementation, can solve VERY simple sudoki
parent
0d63cd7209
commit
1f231d6d40
|
@ -8,3 +8,8 @@ Cargo.lock
|
|||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
|
||||
#Added by cargo
|
||||
|
||||
/target
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
[package]
|
||||
name = "sudoku_solver"
|
||||
version = "0.1.0"
|
||||
authors = ["Recognition2 <gkh998@gmail.com>"]
|
||||
authors = ["Kevin Hill <gkh998@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.9.0"
|
||||
nalgebra = "0.21.0"
|
||||
rayon = "1.3.0"
|
||||
|
|
287
src/main.rs
287
src/main.rs
|
@ -1,136 +1,227 @@
|
|||
#![feature(vec_remove_item)]
|
||||
use itertools::Itertools;
|
||||
use nalgebra::{Dynamic, Matrix, MatrixN, MatrixSlice, MatrixSliceN, U1, U3, U9};
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
convert::{identity, TryInto},
|
||||
fmt, fs,
|
||||
num::NonZeroU8,
|
||||
};
|
||||
|
||||
type MayU8 = Option<NonZeroU8>;
|
||||
type Data = MatrixN<MayU8, U9>;
|
||||
type Quadrant<'a> = MatrixSlice<'a, MayU8, Dynamic, Dynamic, U1, U9>;
|
||||
|
||||
use std::io::Error;
|
||||
use std::fs;
|
||||
use std::fmt;
|
||||
// type Sudoku = Vec<Vec<Option<u32>>>;
|
||||
|
||||
struct Sudoku(Vec<Vec<Option<u32>>>);
|
||||
struct Sudoku(Data);
|
||||
|
||||
impl fmt::Display for Sudoku {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut lines = 1;
|
||||
let mut chars = 1;
|
||||
for row in self.0.iter() {
|
||||
for num in row {
|
||||
match num {
|
||||
None => write!(f, " ")?,
|
||||
Some(x) => write!(f, "{}", x)?
|
||||
};
|
||||
if chars % 3 == 0 {
|
||||
for (j, r) in self.0.column_iter().enumerate() {
|
||||
for (i, el) in r.iter().enumerate() {
|
||||
match el {
|
||||
None => write!(f, "\x1b[0;31m- \x1b[0m")?,
|
||||
Some(el) => write!(f, "{} ", el)?,
|
||||
}
|
||||
if i % 3 == 2 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
chars += 1;
|
||||
}
|
||||
if lines % 3 == 0 {
|
||||
write!(f, "\n")?;
|
||||
writeln!(f, "")?;
|
||||
if j % 3 == 2 {
|
||||
writeln!(f, "")?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
lines += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sudoku {
|
||||
|
||||
pub fn from_file(file: String) -> Option<Sudoku> {
|
||||
Some(Sudoku(Data::from_iterator(
|
||||
fs::read_to_string(file)
|
||||
.ok()?
|
||||
.lines()
|
||||
.filter(|l| l.len() > 0)
|
||||
.flat_map(|l| {
|
||||
l.chars().filter(|&c| c != ' ').map(|c| {
|
||||
c.to_digit(10)
|
||||
.map(|c| c.try_into().unwrap())
|
||||
.map(|c| NonZeroU8::new(c).unwrap())
|
||||
})
|
||||
}),
|
||||
)))
|
||||
}
|
||||
fn is_solved(&self) -> bool {
|
||||
self.0.iter()
|
||||
.flat_map(|row| row
|
||||
.iter()
|
||||
.filter(|&&el| el.is_none()))
|
||||
.count() == 0
|
||||
self.is_partial_valid() && self.0.iter().filter(|i| i.is_none()).count() == 0
|
||||
}
|
||||
fn is_partial_valid(&self) -> bool {
|
||||
let row_correct = self.0.row_iter().all(|r| {
|
||||
r.iter()
|
||||
.filter_map(|&i| i)
|
||||
.sorted()
|
||||
.fold((0, true), |acc, new| (new.get(), acc.1 && new.get() > acc.0))
|
||||
.1
|
||||
});
|
||||
let col_correct = self.0.column_iter().all(|r| {
|
||||
r.iter()
|
||||
.filter_map(|&i| i)
|
||||
.sorted()
|
||||
.fold((0, true), |acc, new| (new.get(), acc.1 && new.get() > acc.0))
|
||||
.1
|
||||
});
|
||||
let quadrants_correct = (0..3)
|
||||
.flat_map(|i| (0..3).map(move |j| self.get_quadrant(i, j)))
|
||||
.all(|r| {
|
||||
r.iter()
|
||||
.filter_map(|&i| i)
|
||||
.sorted()
|
||||
.fold((0, true), |acc, new| (new.get(), acc.1 && new.get() > acc.0))
|
||||
.1
|
||||
});
|
||||
|
||||
row_correct && col_correct && quadrants_correct
|
||||
}
|
||||
|
||||
fn is_correct(&self) -> bool {
|
||||
for i in 0..self.0.len() {
|
||||
for j in 0..self.0[0].len() {
|
||||
|
||||
fn count_correct(&self) -> usize {
|
||||
self.0.iter().filter(|el| el.is_some()).count()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
fn get_quadrant(&self, i: usize, j: usize) -> Quadrant {
|
||||
self.0.slice((3 * i, 3 * j), (3, 3))
|
||||
}
|
||||
|
||||
|
||||
let rows_correct = self
|
||||
.0
|
||||
fn quadrant_contains_n(&self, (i, j): (usize, usize), n: u8) -> bool {
|
||||
self.get_quadrant(i, j)
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let mut rowCopy = row.copy();
|
||||
rowCopy.sort_unstable();
|
||||
rowCopy.dedup().len() == row.len()
|
||||
}).all();
|
||||
|
||||
let cols_correct = {
|
||||
let mut sudoku = self.copy();
|
||||
sudoku.transpose();
|
||||
sudoku
|
||||
};
|
||||
.any(|&o: &MayU8| o == Some(NonZeroU8::new(n).unwrap()))
|
||||
}
|
||||
|
||||
/* fn check_rows(&mut self) -> usize {
|
||||
let mut m = &self.0;
|
||||
for i in 1..10 {
|
||||
for j in 1..10 {
|
||||
let mut slice = m[i];
|
||||
if let Some(el) = find_missing(&mut slice) {
|
||||
m.get_mut(i).get_or_insert(Some(el)) = Some(el);
|
||||
m[i][j] = Some(el);
|
||||
fn quadrant_get_coords_n(&self, (i, j): (usize, usize), n: u8) -> Option<(usize, usize)> {
|
||||
let q = self.get_quadrant(i, j);
|
||||
(0..3)
|
||||
.flat_map(|a| (0..3).map(move |b| ((i * 3 + a, j * 3 + b), q[(a, b)])))
|
||||
.filter(|&(_, o)| o == NonZeroU8::new(n))
|
||||
.map(|(ab, _)| ab)
|
||||
.exactly_one()
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn solve_single_digit(&mut self) -> usize {
|
||||
let count = self.count_correct();
|
||||
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
for n in 1..=9 {
|
||||
assert!(self.is_partial_valid(), "Sudoku is no longer valid!");
|
||||
// println!("Trying to solve n={} in quadrant=({}, {})", n, i, j);
|
||||
let current = self.get_quadrant(i, j);
|
||||
if self.quadrant_contains_n((i, j), n) {
|
||||
// This number is already present in this quadrant.
|
||||
continue;
|
||||
}
|
||||
let positions = (0..3)
|
||||
.flat_map(move |k| (0..3).map(move |l| (i * 3 + k, j * 3 + l, current[(k, l)])))
|
||||
.filter(|(_, _, o)| o.is_none())
|
||||
.map(|(a, b, _)| (a, b))
|
||||
// .inspect(|(a, b)| println!(" None found at: ({}, {})", a, b))
|
||||
.filter(|&(a, b)| {
|
||||
self.quadrant_get_coords_n(((i + 1) % 3, j), n)
|
||||
.map(|(i, j)| j != b)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.filter(|&(a, b)| {
|
||||
self.quadrant_get_coords_n(((i + 2) % 3, j), n)
|
||||
.map(|(i, j)| j != b)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.filter(|&(a, b)| {
|
||||
self.quadrant_get_coords_n((i, (j + 1) % 3), n)
|
||||
.map(|(i, j)| i != a)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.filter(|&(a, b)| {
|
||||
self.quadrant_get_coords_n((i, (j + 2) % 3), n)
|
||||
.map(|(i, j)| i != a)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
match positions.len() {
|
||||
0 => panic!("A may analysis can never be too strict"),
|
||||
1 => {
|
||||
let (a, b) = positions[0];
|
||||
println!(" Found an answer: ({}, {}) = {}", a, b, n);
|
||||
self.0[(a, b)] = NonZeroU8::new(n)
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// for row in &self.0 {
|
||||
// if row.iter()
|
||||
// .filter(|l| l.is_none())
|
||||
// .count() == 1 {
|
||||
// // Find element
|
||||
//
|
||||
// }
|
||||
// }
|
||||
3
|
||||
} */
|
||||
}
|
||||
|
||||
|
||||
fn find_missing(v: &mut Vec<Option<u32>>) -> Option<u32> {
|
||||
let mut all: Vec<u32> = (1..10).collect();
|
||||
for i in v.iter() {
|
||||
let _ = match i {
|
||||
Some(v) => all.retain(|e| e != v),
|
||||
None => continue
|
||||
};
|
||||
self.count_correct() - count
|
||||
}
|
||||
if all.len() < 1 {
|
||||
None
|
||||
} else {
|
||||
Some(*all.first().unwrap())
|
||||
|
||||
pub fn solve_by_elimination(&mut self) -> usize {
|
||||
let count = self.count_correct();
|
||||
|
||||
for i in 0..9 {
|
||||
for j in 0..9 {
|
||||
let el = self.0[(i, j)];
|
||||
let mut options = (0..=9).collect_vec();
|
||||
if el.is_none() {
|
||||
self.0
|
||||
.row(i)
|
||||
.iter()
|
||||
.chain(self.0.column(j).iter())
|
||||
.chain({
|
||||
let mut vs = Vec::new();
|
||||
for a in 0..3 {
|
||||
for b in 0..3 {
|
||||
vs.push(self.0.get((i - i % 3 + a, j - j % 3 + b)).unwrap());
|
||||
}
|
||||
}
|
||||
vs.into_iter()
|
||||
})
|
||||
.unique()
|
||||
.filter(|&&v| v != el)
|
||||
.filter_map(|&v| v)
|
||||
.for_each(|v| {
|
||||
options.remove_item(&v.get());
|
||||
});
|
||||
}
|
||||
if options.len() == 1 {
|
||||
println!("There is only 1 option: {}", options[0]);
|
||||
self.0[(i, j)] = NonZeroU8::new(options[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.count_correct() - count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn read_from_file(filename: &str) -> Result<Sudoku, Error> {
|
||||
Ok(Sudoku(fs::read_to_string(filename)?
|
||||
.replace("\n\n", "\n")
|
||||
.lines()
|
||||
.map(|l| l
|
||||
.chars()
|
||||
.filter(|&c| c != ' ')
|
||||
.map(|c| c.to_digit(10))
|
||||
.collect())
|
||||
.collect()))
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
let mut sudoku = read_from_file("sudoku.txt").unwrap();
|
||||
println!("Gelezen");
|
||||
let mut s = Sudoku::from_file("sudoku.txt".to_owned()).unwrap();
|
||||
|
||||
for x in 0..sudoku.0.len() {
|
||||
for y in 0..sudoku.0[0].len() {
|
||||
if
|
||||
println!("Sudoku is \n{}", s);
|
||||
|
||||
// println!("After solving by elimination (solved {} items), we have:\n{}", n, s);
|
||||
let mut solved = 0;
|
||||
loop {
|
||||
let cnt = s.solve_single_digit();
|
||||
assert!(s.is_partial_valid(), "Sudoku is no longer valid!");
|
||||
if cnt == 0 {
|
||||
break;
|
||||
} else {
|
||||
solved += cnt;
|
||||
}
|
||||
println!("Solved {} more ({} total)! Sudoku now looks like:\n{}", cnt, solved, s);
|
||||
}
|
||||
|
||||
|
||||
|
||||
println!(
|
||||
"After solving by single digit(solved {} items), we have:\n{}",
|
||||
solved, s
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
- 4 - - - - 1 7 9
|
||||
- - 2 - - 8 - 5 4
|
||||
- - 6 - - 5 - - 8
|
||||
- 8 - - 7 - 9 1 -
|
||||
- 5 - - 9 - - 3 -
|
||||
- 1 9 - 6 - - 4 -
|
||||
3 - - 4 - - 7 - -
|
||||
5 7 - 1 - - 2 - -
|
||||
9 2 8 - - - - 6 -
|
||||
|
||||
|
Loading…
Reference in New Issue