This post contains some preliminary notes on topics that I’ll discuss better in future.
This is a little but fast enough Sudoku solver written in C (modified from here: http://rosettacode.org/wiki/Sudoku#C ):
#include <stdio.h>
void show(int *x)
{
int i, j;
for (i = 0; i < 9; i++) {
if (!(i % 3)) putchar('\n');
for (j = 0; j < 9; j++)
printf(j % 3 ? "%2d" : "%3d", *x++);
putchar('\n');
}
}
int trycell(int *x, int pos)
{
int row = pos / 9;
int col = pos % 9;
int i, j, used = 0;
if (pos == 81) return 1;
if (x[pos]) return trycell(x, pos + 1);
for (i = 0; i < 9; i++)
used |= 1 << (x[i * 9 + col] - 1);
for (j = 0; j < 9; j++)
used |= 1 << (x[row * 9 + j] - 1);
row = row / 3 * 3;
col = col / 3 * 3;
for (i = row; i < row + 3; i++)
for (j = col; j < col + 3; j++)
used |= 1 << (x[i * 9 + j] - 1);
for (x[pos] = 1; x[pos] <= 9; x[pos]++, used >>= 1)
if (!(used & 1) && trycell(x, pos + 1)) return 1;
x[pos] = 0;
return 0;
}
void solve(const char *s)
{
int i, x[81];
for (i = 0; i < 81; i++)
x[i] = s[i] >= '1' && s[i] <= '9' ? s[i] - '0' : 0;
if (trycell(x, 0))
show(x);
else
puts("no solution");
}
int main(void)
{
int i;
for (i = 0; i < 1000; i++)
solve( "5x..7...."
"6..195..."
".98....6."
"8...6...3"
"4..8.3..1"
"7...2...6"
".6....28."
"...419..5"
"....8..79" );
return 0;
}
A Rust port:
const N: usize = 9;
const NN: usize = N * N;
type Table = [u32; NN];
fn show_table(tab: &Table) {
let mut pos = 0;
for i in 0 .. N {
if i % 3 == 0 {
print!("\n");
}
for j in 0 .. N {
if j % 3 == 0 {
print!("{:3}", tab[pos]);
} else {
print!("{:2}", tab[pos]);
}
pos += 1;
}
print!("\n");
}
}
fn try_cell(tab: &mut Table, pos: usize) -> bool {
unsafe {
if pos == NN {
return true;
}
if *tab.get_unchecked(pos) != 0 {
return try_cell(tab, pos + 1);
}
let row = pos / N;
let col = pos % N;
let mut used: u32 = 0;
for i in 0 .. N {
used |= 1u32.wrapping_shl(tab.get_unchecked(i * N + col).wrapping_sub(1));
}
for j in 0 .. N {
used |= 1u32.wrapping_shl(tab.get_unchecked(row * N + j).wrapping_sub(1));
}
let round_row = row / 3 * 3;
let round_col = col / 3 * 3;
for i in round_row .. round_row + 3 {
for j in round_col .. round_col + 3 {
used |= 1u32.wrapping_shl(tab.get_unchecked(i * N + j).wrapping_sub(1));
}
}
*tab.get_unchecked_mut(pos) = 1;
while *tab.get_unchecked(pos) <= N as u32 {
if used & 1 == 0 && try_cell(tab, pos + 1) {
return true;
}
*tab.get_unchecked_mut(pos) += 1;
used >>= 1;
}
*tab.get_unchecked_mut(pos) = 0;
false
}
}
fn solve(s: &[u8]) {
if s.len() != NN {
println!("Wrong input length.");
return;
}
let mut tab: Table = [Default::default(); NN];
for i in 0 .. NN {
tab[i] = (s[i] as char).to_digit(10).unwrap_or(0);
}
if try_cell(&mut tab, 0) {
show_table(&tab);
} else {
println!("No solution.");
}
}
fn main() {
for _ in 0 .. 1_000 {
solve(b"\
5x..7....\
6..195...\
.98....6.\
8...6...3\
4..8.3..1\
7...2...6\
.6....28.\
...419..5\
....8..79");
}
}
I compile the code with:
gcc v. 4.9.1
rustc 1.10.0-nightly 2016-04-20
rustc -C opt-level=3 -C prefer-dynamic -C target-cpu=native sudoku2.rs
gcc -Ofast -flto -Wall sudoku1.c -o sudoku1
The C version (with 1000 loops) runs in about 0.24 seconds, while the Rust version in about 0.26 seconds, so their run times are close enough.
But If I replace the get_unchecked/get_unchecked_mut with normal safe array accesses, the run-time of the Rust code is about 0.31 seconds. For some kind of usages this is a bit too much difference in run time.
Probably you can write down proofs that the array accesses in that try_cell() function are always in-bound (and perhaps languages like Dafny or Whiley are able to prove it after you have written some contracts and invariants). But I think there are features that are simpler to implement and to use, and faster to compile, that are able to remove all (or some) array bound tests in the try_cell() function. I’d like some of such features (tied together) in Rust, partially derived from Ada language and other sources, that should help improve safety and/or efficiency of Rust code. I think it’s quite important for the design purposes of Rust to allow to write programs that are both safe and very fast.
I’ve seen that the compiler seems able to remove the array bound tests for the “pos” index in try_cell() function, but not the others three array accesses inside try_cell(). The other missing three can be removed with Value Range Analysis.
Beside Value Range Analysis (and Slice Range Length Analysis) and few other features, a couple array/slice methods could be useful:
get_verified()
get_verified_mut()
They access the array without run-time bound tests, but they give a compilation error if the compiler isn’t statically able to see that the index will never access out of the bounds at runtime. So they are always safe and always fast, if they compile.