From 8c92d250ac67da438430919fc7b0bb5b88564e1f Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Sat, 6 Apr 2024 09:36:02 +0700 Subject: [PATCH 1/8] chore: add `trapped_rainwater.rs` to DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index d2b7f13b6d7..46af485f6c7 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -92,6 +92,7 @@ * [Rod Cutting](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/rod_cutting.rs) * [Snail](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/snail.rs) * [Subset Generation](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/subset_generation.rs) + * [Trapped Rain Water](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/trapped_rainwater.rs) * [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs) * General * [Convex Hull](https://github.com/TheAlgorithms/Rust/blob/master/src/general/convex_hull.rs) From ca0947ee379ec6465321eeeee1e994c35f159d61 Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Sat, 6 Apr 2024 09:36:34 +0700 Subject: [PATCH 2/8] feat: implement Trapped Rain Water algorithm --- src/dynamic_programming/mod.rs | 2 + src/dynamic_programming/trapped_rainwater.rs | 83 ++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/dynamic_programming/trapped_rainwater.rs diff --git a/src/dynamic_programming/mod.rs b/src/dynamic_programming/mod.rs index ad97d345855..76059465899 100644 --- a/src/dynamic_programming/mod.rs +++ b/src/dynamic_programming/mod.rs @@ -15,6 +15,7 @@ mod minimum_cost_path; mod rod_cutting; mod snail; mod subset_generation; +mod trapped_rainwater; mod word_break; pub use self::coin_change::coin_change; @@ -41,4 +42,5 @@ pub use self::minimum_cost_path::minimum_cost_path; pub use self::rod_cutting::rod_cut; pub use self::snail::snail; pub use self::subset_generation::list_subset; +pub use self::trapped_rainwater::trapped_rainwater; pub use self::word_break::word_break; diff --git a/src/dynamic_programming/trapped_rainwater.rs b/src/dynamic_programming/trapped_rainwater.rs new file mode 100644 index 00000000000..56c3b8947e1 --- /dev/null +++ b/src/dynamic_programming/trapped_rainwater.rs @@ -0,0 +1,83 @@ +//! Module to calculate trapped rainwater in an elevation map. + +/// Calculates the total amount of trapped rainwater in the given elevation map. +/// +/// # Arguments +/// +/// * `height` - A vector containing the heights of walls forming the elevation map. +/// +/// # Returns +/// +/// The total amount of trapped rainwater. +pub fn trapped_rainwater(height: Vec) -> u32 { + if height.is_empty() { + return 0; + } + + let mut left_max = vec![0; height.len()]; + let mut right_max = vec![0; height.len()]; + let mut water_trapped = 0; + + // Calculate left_max array + left_max[0] = height[0]; + for i in 1..height.len() { + left_max[i] = left_max[i - 1].max(height[i]); + } + + // Calculate right_max array + right_max[height.len() - 1] = height[height.len() - 1]; + for i in (0..(height.len() - 1)).rev() { + right_max[i] = right_max[i + 1].max(height[i]); + } + + // Calculate trapped water + for i in 0..height.len() { + water_trapped += left_max[i].min(right_max[i]) - height[i]; + } + + water_trapped +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! trapped_rainwater_tests { + ($($name:ident: $test_case:expr,)*) => { + $( + #[test] + fn $name() { + let (height, expected_trapped_water) = $test_case; + assert_eq!(trapped_rainwater(height), expected_trapped_water); + } + )* + }; + } + + trapped_rainwater_tests! { + test_trapped_rainwater_basic: ( + vec![0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], + 6 + ), + test_trapped_rainwater_empty: ( + Vec::new(), + 0 + ), + test_trapped_rainwater_flat: ( + vec![0, 0, 0, 0, 0], + 0 + ), + test_trapped_rainwater_no_trapped_water: ( + vec![1, 1, 2, 4, 0, 0, 0], + 0 + ), + test_trapped_rainwater_single_height: ( + vec![5], + 0 + ), + test_trapped_rainwater_large_height_difference: ( + vec![5, 1, 6, 1, 7, 1, 8], + 15 + ), + } +} From c207d307d4a82db7ce9df6f93cc58d0422b1868e Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Tue, 14 May 2024 07:55:03 +0700 Subject: [PATCH 3/8] chore: add tests --- src/dynamic_programming/trapped_rainwater.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/dynamic_programming/trapped_rainwater.rs b/src/dynamic_programming/trapped_rainwater.rs index 56c3b8947e1..2c0263d36ce 100644 --- a/src/dynamic_programming/trapped_rainwater.rs +++ b/src/dynamic_programming/trapped_rainwater.rs @@ -59,6 +59,14 @@ mod tests { vec![0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6 ), + test_bucket: ( + vec![5, 1, 5], + 4 + ), + test_skewed_bucket: ( + vec![4, 1, 5], + 3 + ), test_trapped_rainwater_empty: ( Vec::new(), 0 From 41a41e9f580373e737ef04191ef9824452c3773a Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Tue, 14 May 2024 07:58:52 +0700 Subject: [PATCH 4/8] chore: rename `height` to `elevation_map` --- src/dynamic_programming/trapped_rainwater.rs | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/dynamic_programming/trapped_rainwater.rs b/src/dynamic_programming/trapped_rainwater.rs index 2c0263d36ce..1c27dd0bf45 100644 --- a/src/dynamic_programming/trapped_rainwater.rs +++ b/src/dynamic_programming/trapped_rainwater.rs @@ -4,35 +4,35 @@ /// /// # Arguments /// -/// * `height` - A vector containing the heights of walls forming the elevation map. +/// * `elevation_map` - A vector containing the heights of walls forming the elevation map. /// /// # Returns /// /// The total amount of trapped rainwater. -pub fn trapped_rainwater(height: Vec) -> u32 { - if height.is_empty() { +pub fn trapped_rainwater(elevation_map: Vec) -> u32 { + if elevation_map.is_empty() { return 0; } - let mut left_max = vec![0; height.len()]; - let mut right_max = vec![0; height.len()]; + let mut left_max = vec![0; elevation_map.len()]; + let mut right_max = vec![0; elevation_map.len()]; let mut water_trapped = 0; // Calculate left_max array - left_max[0] = height[0]; - for i in 1..height.len() { - left_max[i] = left_max[i - 1].max(height[i]); + left_max[0] = elevation_map[0]; + for i in 1..elevation_map.len() { + left_max[i] = left_max[i - 1].max(elevation_map[i]); } // Calculate right_max array - right_max[height.len() - 1] = height[height.len() - 1]; - for i in (0..(height.len() - 1)).rev() { - right_max[i] = right_max[i + 1].max(height[i]); + right_max[elevation_map.len() - 1] = elevation_map[elevation_map.len() - 1]; + for i in (0..(elevation_map.len() - 1)).rev() { + right_max[i] = right_max[i + 1].max(elevation_map[i]); } // Calculate trapped water - for i in 0..height.len() { - water_trapped += left_max[i].min(right_max[i]) - height[i]; + for i in 0..elevation_map.len() { + water_trapped += left_max[i].min(right_max[i]) - elevation_map[i]; } water_trapped @@ -47,8 +47,8 @@ mod tests { $( #[test] fn $name() { - let (height, expected_trapped_water) = $test_case; - assert_eq!(trapped_rainwater(height), expected_trapped_water); + let (elevation_map, expected_trapped_water) = $test_case; + assert_eq!(trapped_rainwater(elevation_map), expected_trapped_water); } )* }; @@ -79,11 +79,11 @@ mod tests { vec![1, 1, 2, 4, 0, 0, 0], 0 ), - test_trapped_rainwater_single_height: ( + test_trapped_rainwater_single_elevation_map: ( vec![5], 0 ), - test_trapped_rainwater_large_height_difference: ( + test_trapped_rainwater_large_elevation_map_difference: ( vec![5, 1, 6, 1, 7, 1, 8], 15 ), From d5051501a6f1759af8913cc14b98cafd5d3a820b Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Tue, 14 May 2024 08:19:08 +0700 Subject: [PATCH 5/8] ref: change `Vec) -> u32 { +pub fn trapped_rainwater(elevation_map: &[u32]) -> u32 { if elevation_map.is_empty() { return 0; } - let mut left_max = vec![0; elevation_map.len()]; - let mut right_max = vec![0; elevation_map.len()]; + let left_max = calculate_max(elevation_map, true); + let right_max = calculate_max(elevation_map, false); let mut water_trapped = 0; - // Calculate left_max array - left_max[0] = elevation_map[0]; - for i in 1..elevation_map.len() { - left_max[i] = left_max[i - 1].max(elevation_map[i]); - } - - // Calculate right_max array - right_max[elevation_map.len() - 1] = elevation_map[elevation_map.len() - 1]; - for i in (0..(elevation_map.len() - 1)).rev() { - right_max[i] = right_max[i + 1].max(elevation_map[i]); - } - // Calculate trapped water for i in 0..elevation_map.len() { water_trapped += left_max[i].min(right_max[i]) - elevation_map[i]; @@ -38,6 +26,37 @@ pub fn trapped_rainwater(elevation_map: Vec) -> u32 { water_trapped } +/// Calculates the maximum array of the given elevation map based on the direction. +/// +/// # Arguments +/// +/// * `elevation_map` - A reference to a slice representing the elevation map where each element +/// represents the height of the terrain at that position. +/// * `direction` - A boolean indicating whether to calculate the maximum array from left to right (`true`) +/// or from right to left (`false`). +/// +/// # Returns +/// +/// A vector representing the maximum array. +fn calculate_max(elevation_map: &[u32], direction: bool) -> Vec { + let mut max_array = vec![0; elevation_map.len()]; + match direction { + true => { + max_array[0] = elevation_map[0]; + for i in 1..elevation_map.len() { + max_array[i] = max_array[i - 1].max(elevation_map[i]); + } + } + false => { + max_array[elevation_map.len() - 1] = elevation_map[elevation_map.len() - 1]; + for i in (0..(elevation_map.len() - 1)).rev() { + max_array[i] = max_array[i + 1].max(elevation_map[i]); + } + } + } + max_array +} + #[cfg(test)] mod tests { use super::*; @@ -48,7 +67,7 @@ mod tests { #[test] fn $name() { let (elevation_map, expected_trapped_water) = $test_case; - assert_eq!(trapped_rainwater(elevation_map), expected_trapped_water); + assert_eq!(trapped_rainwater(&elevation_map), expected_trapped_water); } )* }; @@ -56,35 +75,35 @@ mod tests { trapped_rainwater_tests! { test_trapped_rainwater_basic: ( - vec![0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], + [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6 ), test_bucket: ( - vec![5, 1, 5], + [5, 1, 5], 4 ), test_skewed_bucket: ( - vec![4, 1, 5], + [4, 1, 5], 3 ), test_trapped_rainwater_empty: ( - Vec::new(), + [], 0 ), test_trapped_rainwater_flat: ( - vec![0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], 0 ), test_trapped_rainwater_no_trapped_water: ( - vec![1, 1, 2, 4, 0, 0, 0], + [1, 1, 2, 4, 0, 0, 0], 0 ), test_trapped_rainwater_single_elevation_map: ( - vec![5], + [5], 0 ), test_trapped_rainwater_large_elevation_map_difference: ( - vec![5, 1, 6, 1, 7, 1, 8], + [5, 1, 6, 1, 7, 1, 8], 15 ), } From dc1987f0ca60cc5303e70dd46b47a28690b2deb6 Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Sat, 18 May 2024 10:44:56 +0700 Subject: [PATCH 6/8] ref: refactor implementation - Create iterator in a single function - Add tests - Rewrite docstring --- src/dynamic_programming/trapped_rainwater.rs | 70 +++++++++++--------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/dynamic_programming/trapped_rainwater.rs b/src/dynamic_programming/trapped_rainwater.rs index 1431b7491dd..6a35e403616 100644 --- a/src/dynamic_programming/trapped_rainwater.rs +++ b/src/dynamic_programming/trapped_rainwater.rs @@ -1,60 +1,68 @@ //! Module to calculate trapped rainwater in an elevation map. -/// Calculates the total amount of trapped rainwater in the given elevation map. +/// Computes the total volume of trapped rainwater in a given elevation map. /// /// # Arguments /// -/// * `elevation_map` - A vector containing the heights of walls forming the elevation map. +/// * `elevation_map` - A slice containing the heights of the terrain elevations. /// /// # Returns /// -/// The total amount of trapped rainwater. +/// The total volume of trapped rainwater. pub fn trapped_rainwater(elevation_map: &[u32]) -> u32 { if elevation_map.is_empty() { return 0; } - - let left_max = calculate_max(elevation_map, true); - let right_max = calculate_max(elevation_map, false); + let left_max = calculate_max_values(elevation_map, false); + let right_max = calculate_max_values(elevation_map, true); let mut water_trapped = 0; - // Calculate trapped water for i in 0..elevation_map.len() { water_trapped += left_max[i].min(right_max[i]) - elevation_map[i]; } - water_trapped } -/// Calculates the maximum array of the given elevation map based on the direction. +/// Determines the maximum heights from either direction in the elevation map. +/// +/// # Arguments +/// +/// * `elevation_map` - A slice representing the heights of the terrain elevations. +/// * `reverse` - A boolean that indicates the direction of calculation. +/// - `false` for left-to-right. +/// - `true` for right-to-left. +/// +/// # Returns +/// +/// A vector containing the maximum heights encountered up to each position. +fn calculate_max_values(elevation_map: &[u32], reverse: bool) -> Vec { + let mut max_values = vec![0; elevation_map.len()]; + let mut current_max = 0; + for i in create_iter(elevation_map.len(), reverse) { + current_max = current_max.max(elevation_map[i]); + max_values[i] = current_max; + } + max_values +} + +/// Creates an iterator for the given length, optionally reversing it. /// /// # Arguments /// -/// * `elevation_map` - A reference to a slice representing the elevation map where each element -/// represents the height of the terrain at that position. -/// * `direction` - A boolean indicating whether to calculate the maximum array from left to right (`true`) -/// or from right to left (`false`). +/// * `len` - The length of the iterator. +/// * `reverse` - A boolean that determines the order of iteration. +/// - `false` for forward iteration. +/// - `true` for reverse iteration. /// /// # Returns /// -/// A vector representing the maximum array. -fn calculate_max(elevation_map: &[u32], direction: bool) -> Vec { - let mut max_array = vec![0; elevation_map.len()]; - match direction { - true => { - max_array[0] = elevation_map[0]; - for i in 1..elevation_map.len() { - max_array[i] = max_array[i - 1].max(elevation_map[i]); - } - } - false => { - max_array[elevation_map.len() - 1] = elevation_map[elevation_map.len() - 1]; - for i in (0..(elevation_map.len() - 1)).rev() { - max_array[i] = max_array[i + 1].max(elevation_map[i]); - } - } +/// A boxed iterator that iterates over the range of indices. +fn create_iter(len: usize, reverse: bool) -> Box> { + if reverse { + Box::new((0..len).rev()) + } else { + Box::new(0..len) } - max_array } #[cfg(test)] @@ -68,6 +76,8 @@ mod tests { fn $name() { let (elevation_map, expected_trapped_water) = $test_case; assert_eq!(trapped_rainwater(&elevation_map), expected_trapped_water); + let elevation_map_rev: Vec = elevation_map.iter().rev().cloned().collect(); + assert_eq!(trapped_rainwater(&elevation_map_rev), expected_trapped_water); } )* }; From ce05eb59aae340506ac3f0635af507867ac2c7be Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Sat, 18 May 2024 09:02:06 +0200 Subject: [PATCH 7/8] tests: add more test cases --- src/dynamic_programming/trapped_rainwater.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/dynamic_programming/trapped_rainwater.rs b/src/dynamic_programming/trapped_rainwater.rs index 6a35e403616..5b254008e07 100644 --- a/src/dynamic_programming/trapped_rainwater.rs +++ b/src/dynamic_programming/trapped_rainwater.rs @@ -88,6 +88,10 @@ mod tests { [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6 ), + test_trapped_rainwater_peak_under_water: ( + [3, 0, 2, 0, 4], + 7, + ), test_bucket: ( [5, 1, 5], 4 @@ -112,6 +116,10 @@ mod tests { [5], 0 ), + test_trapped_rainwater_two_point_elevation_map: ( + [5, 1], + 0 + ), test_trapped_rainwater_large_elevation_map_difference: ( [5, 1, 6, 1, 7, 1, 8], 15 From 4110e15ea6c02f597452845e59163abebe6dba37 Mon Sep 17 00:00:00 2001 From: Piotr Idzik <65706193+vil02@users.noreply.github.com> Date: Sat, 18 May 2024 09:05:12 +0200 Subject: [PATCH 8/8] style: simplify logic by reducing redundant branch --- src/dynamic_programming/trapped_rainwater.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/dynamic_programming/trapped_rainwater.rs b/src/dynamic_programming/trapped_rainwater.rs index 5b254008e07..b220754ca23 100644 --- a/src/dynamic_programming/trapped_rainwater.rs +++ b/src/dynamic_programming/trapped_rainwater.rs @@ -10,9 +10,6 @@ /// /// The total volume of trapped rainwater. pub fn trapped_rainwater(elevation_map: &[u32]) -> u32 { - if elevation_map.is_empty() { - return 0; - } let left_max = calculate_max_values(elevation_map, false); let right_max = calculate_max_values(elevation_map, true); let mut water_trapped = 0;