diff --git a/contents/flood_fill/code/rust/Cargo.toml b/contents/flood_fill/code/rust/Cargo.toml new file mode 100644 index 000000000..8ee1522ca --- /dev/null +++ b/contents/flood_fill/code/rust/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "flood_fill" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ndarray = "0.13.1" + +[[bin]] +path = "./flood_fill.rs" +name = "main" \ No newline at end of file diff --git a/contents/flood_fill/code/rust/flood_fill.rs b/contents/flood_fill/code/rust/flood_fill.rs new file mode 100644 index 000000000..4870a253d --- /dev/null +++ b/contents/flood_fill/code/rust/flood_fill.rs @@ -0,0 +1,151 @@ +use ndarray::prelude::*; // 0.13.1 +use std::collections::VecDeque; + +fn color(canvas: &mut Array, loc: &[usize; 2], old_val: usize, new_val: usize) { + // do not color a nonexistent pixel + if let Some(pixel) = canvas.get(*loc) { + // only change color if the element value is the old value + if pixel == &old_val { + canvas[*loc] = new_val; + } + } +} + +fn find_neighbors(canvas: &Array, loc: &[usize; 2], val: usize) -> Vec<[usize; 2]> { + // find neighbors + let mut possible_neighbors: Vec<[usize; 2]> = vec![[loc[0] + 1, loc[1]], [loc[0], loc[1] + 1]]; + // guard against underflow errors + if loc[0] as isize > 0 { + possible_neighbors.push([loc[0] - 1, loc[1]]); + } + if loc[1] as isize > 0 { + possible_neighbors.push([loc[0], loc[1] - 1]); + } + + // exclude neighbors that are out of bounds and are not the old fill value + possible_neighbors + .into_iter() + .filter(|i| canvas.get(*i) == Some(&val)) + .collect() +} + +fn stack_fill(canvas: &mut Array, loc: &[usize; 2], old_val: usize, new_val: usize) { + // don't do anything if old_val and new_val are identical + if new_val == old_val { + return; + } + + // set up the stack + let mut stack: Vec<[usize; 2]> = Vec::new(); + stack.push(*loc); + + while let Some(current_loc) = stack.pop() { + if canvas[current_loc] == old_val { + color(canvas, ¤t_loc, old_val, new_val); + let neighbors = find_neighbors(canvas, ¤t_loc, old_val); + stack.extend(neighbors); + } + } +} + +fn queue_fill(canvas: &mut Array, loc: &[usize; 2], old_val: usize, new_val: usize) { + // don't do anything if old_val and new_val are identical + if new_val == old_val { + return; + } + + // set up the queue + let mut queue: VecDeque<[usize; 2]> = VecDeque::new(); + queue.push_back(*loc); + + // color the initial location + color(canvas, &loc, old_val, new_val); + + while let Some(current_loc) = queue.pop_front() { + let up_next = find_neighbors(canvas, ¤t_loc, old_val); + // color as we enqueue neighbors + for n in up_next { + color(canvas, &n, old_val, new_val); + queue.push_back(n); + } + } +} + +fn recursive_fill( + canvas: &mut Array, + loc: &[usize; 2], + old_val: usize, + new_val: usize, +) { + // don't do anything if old_val and new_val are identical + if new_val == old_val { + return; + } + + color(canvas, &loc, old_val, new_val); + + let up_next = find_neighbors(canvas, &loc, old_val); + up_next + .iter() + .for_each(|i| recursive_fill(canvas, i, old_val, new_val)); +} + +fn main() { + let grid = array![ + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0] + ]; + + let solution_grid = array![ + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0] + ]; + + let start_loc = [0, 0]; + + let mut recursive_grid = grid.clone(); + recursive_fill(&mut recursive_grid, &start_loc, 0, 1); + + let mut queue_grid = grid.clone(); + queue_fill(&mut queue_grid, &start_loc, 0, 1); + + let mut stack_grid = grid.clone(); + stack_fill(&mut stack_grid, &start_loc, 0, 1); + + println!("Starting grid:"); + println!("{}", grid); + println!(); + println!("Solution grid:"); + println!("{}", solution_grid); + println!(); + println!( + "Recursive grid {} correct.", + if recursive_grid == solution_grid { + "is" + } else { + "is NOT" + } + ); + println!( + "Queue grid {} correct.", + if queue_grid == solution_grid { + "is" + } else { + "is NOT" + } + ); + println!( + "Stack grid {} correct.", + if stack_grid == solution_grid { + "is" + } else { + "is NOT" + } + ); +} diff --git a/contents/flood_fill/flood_fill.md b/contents/flood_fill/flood_fill.md index 836f86897..453f24a73 100644 --- a/contents/flood_fill/flood_fill.md +++ b/contents/flood_fill/flood_fill.md @@ -90,6 +90,8 @@ In code, this might look like this: [import:37-55, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:34-52, lang:"c"](code/c/flood_fill.c) +{% sample lang="rs" %} +[import:14-30, lang:"rust"](code/rust/flood_fill.rs) {% endmethod %} @@ -106,6 +108,8 @@ In code, it might look like this: [import:106-118, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:180-195, lang:"c"](code/c/flood_fill.c) +{% sample lang="rs" %} +[import:74-91, lang:"rust"](code/rust/flood_fill.rs) {% endmethod %} All code snippets for this chapter rely on an exterior `color` function, defined as @@ -115,6 +119,8 @@ All code snippets for this chapter rely on an exterior `color` function, defined [import:23-35, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:28-32, lang:"c"](code/c/flood_fill.c) +{% sample lang="rs" %} +[import:4-12, lang:"rust"](code/rust/flood_fill.rs) {% endmethod %} The above code continues recursing through available neighbors as long as neighbors exist, and this should work so long as we are adding the correct set of neighbors. @@ -126,6 +132,8 @@ Additionally, it is possible to do the same type of traversal by managing a stac [import:57-77, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:85-108, lang:"c"](code/c/flood_fill.c) +{% sample lang="rs" %} +[import:32-49, lang:"rust"](code/rust/flood_fill.rs) {% endmethod %} This is ultimately the same method of traversal as before; however, because we are managing our own data structure, there are a few distinct differences: @@ -165,6 +173,8 @@ The code would look something like this: [import:80-104, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import:155-178, lang:"c"](code/c/flood_fill.c) +{% sample lang="rs"%} +[import:51-72, lang:"rust"](code/rust/flood_fill.rs) {% endmethod %} Now, there is a small trick in this code that must be considered to make sure it runs optimally. @@ -243,6 +253,8 @@ After, we will fill in the left-hand side of the array to be all ones by choosin [import, lang:"julia"](code/julia/flood_fill.jl) {% sample lang="c" %} [import, lang:"c"](code/c/flood_fill.c) +{% sample lang="rs" %} +[import, lang:"rust"](code/rust/flood_fill.rs) {% endmethod %}