refactor flood fill to use a more optimal algorithm
This commit is contained in:
parent
5f653db2c9
commit
5e069cf3ff
|
@ -405,6 +405,7 @@ dependencies = [
|
|||
"criterion",
|
||||
"hashbrown 0.13.2",
|
||||
"mino",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ mino = { path = "../mino" }
|
|||
ahash = "0.8"
|
||||
bumpalo = "3.12"
|
||||
hashbrown = "0.13"
|
||||
smallvec = "1.10"
|
||||
tracing = { version = "0.1", default_features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use core::ops::Range;
|
||||
use mino::matrix::EMPTY_ROW;
|
||||
use mino::matrix::{EMPTY_ROW, FULL_ROW};
|
||||
use mino::{Mat, MatBuf};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// This is the algorithm that mystery and I developed. It computes the "minimum downstack
|
||||
/// estimate", which approximates the minimum pieces to downstack a given board state. It
|
||||
|
@ -100,7 +101,8 @@ fn residue(mat: &Mat) -> Option<Range<i16>> {
|
|||
/// `(x,y,area)` where `(x,y)` is first point in the region filled in, and `area` is the
|
||||
/// number of empty cells filled.
|
||||
fn flood_fill(rows: &mut [u16]) -> Option<(i16, i16, u32)> {
|
||||
fn init(rows: &[u16]) -> Option<(i16, i16)> {
|
||||
// find an empty cell in the matrix somewhere
|
||||
fn find_empty(rows: &[u16]) -> Option<(i16, i16)> {
|
||||
for (y, row) in rows.iter().enumerate() {
|
||||
// trailing_ones() finds some unoccipied cell in `row`. if the row is full
|
||||
// then this return 16 (since there are 16 bits in the row representation
|
||||
|
@ -113,37 +115,67 @@ fn flood_fill(rows: &mut [u16]) -> Option<(i16, i16, u32)> {
|
|||
None
|
||||
}
|
||||
|
||||
fn flood(rows: &mut [u16], x: i16, y: i16) -> u32 {
|
||||
// test if (x,y) is OOB
|
||||
if x < 0 || y < 0 || (y as usize) >= rows.len() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// try to fill in (x,y)
|
||||
let idx = y as usize;
|
||||
let mask = 1 << x;
|
||||
if rows[idx] & mask != 0 {
|
||||
return 0;
|
||||
}
|
||||
rows[idx] |= mask;
|
||||
|
||||
// FIXME: improve this:
|
||||
// - the max recursion depth is small (~400?) so we should use an explicit stack
|
||||
// - scan filling could be more efficient than plain recursion
|
||||
1 + flood(rows, x - 1, y)
|
||||
+ flood(rows, x + 1, y)
|
||||
+ flood(rows, x, y - 1)
|
||||
+ flood(rows, x, y + 1)
|
||||
// given a column in the row that is empty, find the left and rightmost columns that
|
||||
// are also empty and adjacent to to it.
|
||||
fn find_span(row: u16, x: i16) -> (i16, i16) {
|
||||
let left = row & !(FULL_ROW << x);
|
||||
let right = row & !((1 << x) - 1);
|
||||
let lx = 16 - left.leading_zeros() as i16;
|
||||
let rx = right.trailing_zeros() as i16;
|
||||
(lx, rx)
|
||||
}
|
||||
|
||||
let (x0, y0) = init(rows)?;
|
||||
Some((x0, y0, flood(rows, x0, y0)))
|
||||
// list the new columns that should be passed on the stack for the given row and range
|
||||
fn scan(row: u16, lx: i16, rx: i16) -> impl Iterator<Item = i16> {
|
||||
let mut added = false;
|
||||
(lx..rx).filter_map(move |x| {
|
||||
if row & (1 << x) != 0 {
|
||||
added = false;
|
||||
None
|
||||
} else if added {
|
||||
None
|
||||
} else {
|
||||
added = true;
|
||||
Some(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut area = 0;
|
||||
let (x0, y0) = find_empty(rows)?;
|
||||
|
||||
type Stack = SmallVec<[(i16, i16); 4]>;
|
||||
let mut stack = Stack::new();
|
||||
stack.push((x0, y0));
|
||||
|
||||
while let Some((x, y)) = stack.pop() {
|
||||
let row = &mut rows[y as usize];
|
||||
|
||||
let (lx, rx) = find_span(*row, x);
|
||||
area += (rx - lx) as u32;
|
||||
*row |= (FULL_ROW << lx) & !(FULL_ROW << rx);
|
||||
|
||||
if y > 0 {
|
||||
for x in scan(rows[(y - 1) as usize], lx, rx) {
|
||||
stack.push((x, y - 1));
|
||||
}
|
||||
}
|
||||
|
||||
if ((y + 1) as usize) < rows.len() {
|
||||
for x in scan(rows[(y + 1) as usize], lx, rx) {
|
||||
stack.push((x, y + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some((x0, y0, area))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use mino::{mat, matrix::EMPTY_ROW};
|
||||
|
||||
use mino::mat;
|
||||
|
||||
#[test]
|
||||
fn test_flood_fill() {
|
||||
|
|
Loading…
Reference in New Issue