diff --git a/.cargo/config.toml b/.cargo/config.toml
index dc191ce..6dced02 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,3 +1,2 @@
[build]
-target = "x86_64-unknown-linux-musl"
-rustflags = "-C target-cpu=native -C strip=symbols"
+rustflags = ["-C", "target-cpu=native", "-C", "target-feature=+crt-static"]
diff --git a/Cargo.toml b/Cargo.toml
index dc0c9d9..9578e68 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,5 +9,6 @@ edition = "2021"
[profile.release]
lto = true
debug = false
-codegen-units = 1
+strip = true
panic = "abort"
+codegen-units = 1
diff --git a/flamegraph.svg b/flamegraph.svg
new file mode 100644
index 0000000..d488d19
--- /dev/null
+++ b/flamegraph.svg
@@ -0,0 +1,414 @@
+
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 43a9af3..ab091b4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,120 +2,89 @@ use std::time::Instant;
const BLOCK_SIZE: usize = 3;
const SIZE: usize = BLOCK_SIZE * BLOCK_SIZE;
+const NUM_FIELDS: usize = SIZE * SIZE;
struct SField {
field: Vec,
- fixed: Vec,
- size: usize,
- block_size: usize,
+ skipf: Vec,
+ skipb: Vec,
pos: usize,
- pos_last_placed: usize,
- num_fields: usize,
possible_values: Vec>,
}
-/// Some test fields:
-///
-/// EMPTY FIELD
-/// let field = vec![0; SIZE * SIZE];
-///
-/// MANY SOLUTIONS
-/// #[rustfmt::skip]
-/// let field = vec![
-/// 0,0,0,0,0,0,0,0,9,
-/// 0,0,0,0,8,9,0,2,0,
-/// 0,0,0,0,2,0,4,0,0,
-/// 0,0,4,0,6,0,0,0,8,
-/// 0,0,0,5,0,0,0,0,0,
-/// 0,6,5,0,0,2,0,7,4,
-/// 0,3,0,0,0,5,0,4,0,
-/// 0,0,1,8,0,0,0,0,0,
-/// 0,0,8,2,0,0,0,6,0,
-/// ];
-///
-/// HARD
-/// #[rustfmt::skip]
-/// let field = vec![
-/// 8,4,0,0,6,0,5,0,1,
-/// 0,0,0,0,0,3,0,4,0,
-/// 0,0,6,9,0,0,0,0,7,
-/// 0,2,0,7,1,0,0,0,6,
-/// 0,0,0,6,3,0,0,0,0,
-/// 9,0,0,0,0,0,0,5,0,
-/// 0,0,0,0,4,0,0,6,0,
-/// 2,0,0,0,0,0,1,8,0,
-/// 0,0,5,0,0,0,3,0,0,
-/// ];
-///
-/// EASY
-/// #[rustfmt::skip]
-/// let field = vec![
-/// 5,9,0,6,1,3,0,0,0,
-/// 0,0,0,9,0,0,5,0,0,
-/// 8,0,3,0,5,7,6,4,0,
-/// 0,7,5,0,0,0,4,0,6,
-/// 0,6,0,7,4,0,2,0,8,
-/// 2,0,8,0,0,0,7,5,3,
-/// 0,0,0,5,6,1,0,7,0,
-/// 0,0,1,0,7,0,9,0,0,
-/// 7,3,6,0,2,0,0,0,0,
-/// ];
-///
-/// // BLOCK_SIZE 4 MANY SOLUTIONS (DOESN'T FINISH)
-/// #[rustfmt::skip]
-/// let field = vec![
-/// 0,15,0,0,0,0,11,4,0,5,0,0,0,0,12,8,
-/// 12,0,0,9,0,1,0,5,0,0,8,15,0,0,0,13,
-/// 0,0,3,0,0,10,13,0,0,11,4,0,15,0,6,0,
-/// 13,10,0,11,0,0,0,14,0,3,2,0,0,9,0,0,
-/// 0,0,15,10,8,0,0,0,0,0,14,0,0,6,2,0,
-/// 0,0,14,0,0,0,6,0,0,0,0,0,1,0,0,0,
-/// 0,11,0,8,3,0,15,1,6,0,0,0,0,0,0,7,
-/// 0,6,0,7,0,0,0,0,8,13,0,0,10,0,0,0,
-/// 0,14,0,2,0,0,0,0,3,0,5,0,11,15,9,0,
-/// 0,0,0,12,11,0,2,0,0,0,0,0,0,0,0,0,
-/// 0,8,0,0,6,14,1,0,13,15,0,0,0,0,0,10,
-/// 10,0,0,3,9,0,7,0,0,1,0,0,13,12,8,0,
-/// 7,0,0,0,0,0,14,0,0,0,0,0,0,0,0,9,
-/// 0,3,0,14,0,0,9,6,0,0,0,0,0,4,0,0,
-/// 0,4,6,0,0,7,0,0,0,0,0,0,0,0,0,0,
-/// 5,2,0,0,0,4,10,15,1,0,3,0,0,0,7,0,
-/// ];
-
impl SField {
pub fn new() -> SField {
- let field = vec![0; SIZE * SIZE];
+ #[rustfmt::skip]
+ let field = vec![
+ 0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,8,9,0,0,0,
+ 0,0,0,0,2,0,4,0,0,
+ 0,0,4,0,6,0,0,0,8,
+ 0,0,0,5,0,0,0,0,0,
+ 0,6,5,0,0,2,0,7,4,
+ 0,3,0,0,0,5,0,4,0,
+ 0,0,1,8,0,0,0,0,0,
+ 0,0,8,2,0,0,0,6,0,
+ ];
- let mut fixed = Vec::with_capacity(SIZE * SIZE);
- field.iter().for_each(|e| {
- if *e == 0 {
- fixed.push(false);
- } else {
- fixed.push(true);
+ fn find_fixed_streak_forward(mut idx: usize, field: &[u8]) -> u8 {
+ let mut fixed_count = 1;
+ idx += 1;
+
+ while idx < NUM_FIELDS && field[idx] > 0 {
+ fixed_count += 1;
+ idx += 1;
}
- });
+
+ fixed_count
+ }
+
+ fn find_fixed_streak_backward(mut idx: usize, field: &[u8]) -> u8 {
+ let mut fixed_count = 1;
+ idx -= 1;
+
+ while idx < NUM_FIELDS && field[idx] > 0 {
+ fixed_count += 1;
+ idx -= 1;
+ }
+
+ fixed_count
+ }
+
+ let mut skipf = vec![0; NUM_FIELDS];
+ for (idx, nr) in field.iter().enumerate() {
+ match nr {
+ 0 => skipf[idx] = *nr,
+ _ => skipf[idx] = find_fixed_streak_forward(idx, &field),
+ }
+ }
+
+ let mut skipb = vec![0; NUM_FIELDS];
+ for (idx, nr) in field.iter().enumerate().rev() {
+ match nr {
+ 0 => skipb[idx] = *nr,
+ _ => skipb[idx] = find_fixed_streak_backward(idx, &field),
+ }
+ }
SField {
field,
- fixed,
- size: SIZE,
- block_size: BLOCK_SIZE,
+ skipf,
+ skipb,
pos: 0,
- pos_last_placed: 0,
- num_fields: SIZE * SIZE,
possible_values: vec![vec![]; SIZE * SIZE],
}
}
fn build_possible_values_db(&mut self) {
- for idx in 0..self.num_fields {
- if self.fixed[idx] {
+ for idx in 0..NUM_FIELDS {
+ if self.field[idx] != 0 {
continue;
}
self.pos = idx;
// try all values between 1 and =self.size and remember the good ones
- let mut good_ones = Vec::with_capacity(self.size);
- for nr in 1..=(self.size as u8) {
+ let mut good_ones = Vec::with_capacity(SIZE);
+ for nr in 1..=(SIZE as u8) {
if self.ok(nr) {
good_ones.push(nr);
}
@@ -126,121 +95,59 @@ impl SField {
}
pub fn solve_backtracking(&mut self) -> bool {
- let mut found_solution = false;
-
let mut num_solutions = 0;
- let mut last_num_solutions = num_solutions;
- let mut loop_count = 0;
- let mut last_loop_count = loop_count;
-
- let mut now = Instant::now();
loop {
- loop_count += 1;
- const MILLIS_PER_SEC: u128 = 1_000;
- const UPDATE_DELAY_MS: u128 = 80;
- if loop_count % 1000 == 0 {
- let elapsed_ms = now.elapsed().as_millis();
- if elapsed_ms <= UPDATE_DELAY_MS {
- continue;
- }
- self.print_clear();
- self.print();
- println!(
- "{} loops/sec",
- (loop_count - last_loop_count) * (MILLIS_PER_SEC / elapsed_ms)
- );
- println!(
- "{} solutions/sec",
- (num_solutions - last_num_solutions) * (MILLIS_PER_SEC / elapsed_ms)
- );
- last_loop_count = loop_count;
- last_num_solutions = num_solutions;
- now = Instant::now();
- }
- if self.is_end() {
- found_solution = true;
- num_solutions += 1;
-
- /*
- if num_solutions % 100 == 0 {
- self.print_clear();
- println!("Solutions: {}", num_solutions);
- self.print();
- }
- */
-
- if self.is_fixed() {
- continue;
- }
+ if !self.put_valid_nr() {
self.clear_current_field();
- self.prev();
- self.goto_prev_free_field();
- }
-
- if self.is_fixed() {
- self.next();
+ if !self.prev() {
+ self.print_clear();
+ self.print();
+ println!("Number of solutions: {}", num_solutions);
+ break;
+ }
continue;
}
- if self.goto_next_free_field() {
- if self.put_valid_nr() {
- self.next();
- continue;
- } else {
- //println!("put_valid_nr failed for pos {}", self.pos);
- //std::thread::sleep_ms(300);
- self.clear_current_field();
- if !self.prev() {
- println!("Number of solutions: {}", num_solutions);
- break;
- }
- if !self.goto_prev_free_field() {
- println!("Number of solutions: {}", num_solutions);
- break;
- }
+ if !self.next() {
+ num_solutions += 1;
+ if num_solutions % 10_000 == 0 {
+ self.print_clear();
+ self.print();
+ println!("Number of solutions: {}", num_solutions);
}
+
+ self.clear_current_field();
+ self.prev();
}
}
- found_solution
+ num_solutions > 0
}
- #[inline(always)]
fn print_gray(&self) {
print!("\x1b\x5b\x31\x3b\x33\x30\x6d");
}
- #[inline(always)]
fn print_green(&self) {
print!("\x1b\x5b\x31\x3b\x33\x32\x6d");
}
- #[inline(always)]
- fn print_red(&self) {
- print!("\x1b\x5b\x31\x3b\x33\x31\x6d");
- }
-
- #[inline(always)]
fn print_neutral(&self) {
print!("\x1b\x5b\x31\x3b\x30\x6d");
}
- #[inline(always)]
fn print_clear(&self) {
print!("\x1b\x5b\x48\x1b\x5b\x32\x4a");
}
- #[inline(always)]
fn print(&self) {
- for i in 0..self.num_fields {
- if i != 0 && i % self.size == 0 {
+ for i in 0..NUM_FIELDS {
+ if i != 0 && i % SIZE == 0 {
println!();
}
- if i == self.pos_last_placed {
- self.print_red();
- } else if i == self.pos {
+ if i == self.pos {
self.print_green();
} else if self.get_field_at_pos(i) == 0 {
self.print_gray();
@@ -248,57 +155,17 @@ impl SField {
print!("{:2} ", self.get_field_at_pos(i));
- if i == self.pos || i == self.pos_last_placed || self.get_field_at_pos(i) == 0 {
+ if i == self.pos || self.get_field_at_pos(i) == 0 {
self.print_neutral();
}
}
println!();
}
- #[inline(always)]
fn clear_current_field(&mut self) {
self.set(0);
}
- #[inline(always)]
- fn goto_prev_free_field(&mut self) -> bool {
- while {
- if !self.is_fixed() {
- return true;
- }
- self.prev()
- } {}
-
- false
- }
-
- #[inline(always)]
- fn goto_next_free_field(&mut self) -> bool {
- while {
- if !self.is_fixed() {
- return true;
- }
- self.next()
- } {}
-
- false
- }
-
- #[inline(always)]
- fn _put_valid_nr(&mut self) -> bool {
- let current_nr = self.get_field_at_pos(self.pos);
-
- for nr in current_nr..(self.size + 1) as u8 {
- if self.ok(nr) {
- self.set(nr);
- return true;
- }
- }
-
- false
- }
-
- #[inline(always)]
fn put_valid_nr(&mut self) -> bool {
let current_nr = self.get_field_at_pos(self.pos);
@@ -320,32 +187,10 @@ impl SField {
false
}
- #[inline(always)]
- fn is_end(&self) -> bool {
- !self.field.contains(&0)
- }
-
- #[inline(always)]
fn ok(&self, nr: u8) -> bool {
self.block_ok(nr) && self.row_ok(nr) && self.col_ok(nr)
}
- #[inline(always)]
- fn _pos_to_xy(&self) -> (usize, usize) {
- let y = self.pos / self.size;
- let x = self.pos % self.size;
- (x + 1, y + 1)
- }
-
- #[inline(always)]
- fn is_fixed(&self) -> bool {
- // safety: self.pos can be used to index the field unchecked
- // since the only methods modifying self.pos are
- // `next()` and `prev()` and they do bounds checking
- unsafe { *self.fixed.get_unchecked(self.pos) }
- }
-
- #[inline(always)]
fn get_field_at_pos(&self, pos: usize) -> u8 {
// safety:
// TODO
@@ -354,81 +199,74 @@ impl SField {
unsafe { *self.field.get_unchecked(pos) }
}
- #[inline(always)]
fn set(&mut self, nr: u8) {
self.field[self.pos] = nr;
- self.pos_last_placed = self.pos;
}
- #[inline(always)]
fn get_row(&self, row: &mut [u8; SIZE]) {
for (idx, row_elem) in row.iter_mut().enumerate() {
- *row_elem = self.get_field_at_pos((self.pos / self.size) * self.size + idx);
+ *row_elem = self.get_field_at_pos((self.pos / SIZE) * SIZE + idx);
}
}
- #[inline(always)]
fn get_col(&self, col: &mut [u8; SIZE]) {
for (idx, col_elem) in col.iter_mut().enumerate() {
- *col_elem = self.get_field_at_pos(idx * self.size + self.pos % self.size);
+ *col_elem = self.get_field_at_pos(idx * SIZE + self.pos % SIZE);
}
}
- #[inline(always)]
fn get_block(&self, block: &mut [u8; SIZE]) {
- let block_start_row = self.pos / self.size / self.block_size * self.block_size;
- let block_start_col = self.pos % self.size / self.block_size * self.block_size;
+ let block_start_row = self.pos / SIZE / BLOCK_SIZE * BLOCK_SIZE;
+ let block_start_col = self.pos % SIZE / BLOCK_SIZE * BLOCK_SIZE;
- for r in 0..self.block_size {
- for c in 0..self.block_size {
- block[r * self.block_size + c] =
- self.get_field_at_pos(self.size * (block_start_row + r) + block_start_col + c);
+ for r in 0..BLOCK_SIZE {
+ for c in 0..BLOCK_SIZE {
+ block[r * BLOCK_SIZE + c] =
+ self.get_field_at_pos(SIZE * (block_start_row + r) + block_start_col + c);
}
}
}
- #[inline(always)]
fn block_ok(&self, nr: u8) -> bool {
let mut block = [0; SIZE];
self.get_block(&mut block);
!block.contains(&nr)
}
- #[inline(always)]
fn row_ok(&self, nr: u8) -> bool {
let mut row = [0; SIZE];
self.get_row(&mut row);
!row.contains(&nr)
}
- #[inline(always)]
fn col_ok(&self, nr: u8) -> bool {
let mut col = [0; SIZE];
self.get_col(&mut col);
!col.contains(&nr)
}
- #[inline(always)]
fn next(&mut self) -> bool {
- if self.pos == self.num_fields - 1 {
+ let new_pos = self.pos + 1 + unsafe { *self.skipf.get_unchecked(self.pos + 1) as usize };
+
+ if new_pos >= NUM_FIELDS {
return false;
}
- self.pos += 1;
+ self.pos = new_pos;
true
}
- #[inline(always)]
fn prev(&mut self) -> bool {
- if self.pos == 0 {
+ let new_pos = self.pos - 1 - unsafe { *self.skipb.get_unchecked(self.pos - 1) as usize };
+
+ if new_pos >= NUM_FIELDS {
return false;
}
- self.pos -= 1;
+ self.pos = new_pos;
true
}
- #[inline(always)]
fn solve(&mut self) {
let now = Instant::now();
if !self.solve_backtracking() {