Initial implementation, can solve VERY simple sudoki

master
Kevin Hill 2020-04-19 23:56:06 +02:00
parent 0d63cd7209
commit 1f231d6d40
Signed by: gregory
GPG Key ID: 5C3DF72C0C90FFDE
4 changed files with 212 additions and 99 deletions

5
.gitignore vendored
View File

@ -8,3 +8,8 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
#Added by cargo
/target

View File

@ -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"

View File

@ -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
);
}

11
sudoku.txt Normal file
View File

@ -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 -