From c98744f106d25da86122a3c0cbdb34e8ca221ce9 Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Sun, 10 Nov 2024 10:16:15 +0700 Subject: [PATCH 1/2] ref: refactor minimum cost path --- src/dynamic_programming/minimum_cost_path.rs | 186 ++++++++++++------- 1 file changed, 123 insertions(+), 63 deletions(-) diff --git a/src/dynamic_programming/minimum_cost_path.rs b/src/dynamic_programming/minimum_cost_path.rs index f352965a6fd..7f14529e56b 100644 --- a/src/dynamic_programming/minimum_cost_path.rs +++ b/src/dynamic_programming/minimum_cost_path.rs @@ -1,80 +1,140 @@ -/// Minimum Cost Path via Dynamic Programming - -/// Find the minimum cost traced by all possible paths from top left to bottom right in -/// a given matrix, by allowing only right and down movement - -/// For example, in matrix, -/// [2, 1, 4] -/// [2, 1, 3] -/// [3, 2, 1] -/// The minimum cost path is 7 - -/// # Arguments: -/// * `matrix` - The input matrix. -/// # Complexity -/// - time complexity: O( rows * columns ), -/// - space complexity: O( rows * columns ) use std::cmp::min; -pub fn minimum_cost_path(mut matrix: Vec>) -> usize { - // Add rows and columns variables for better readability - let rows = matrix.len(); - let columns = matrix[0].len(); - - // Preprocessing the first row - for i in 1..columns { - matrix[0][i] += matrix[0][i - 1]; - } - - // Preprocessing the first column - for i in 1..rows { - matrix[i][0] += matrix[i - 1][0]; - } +/// Computes the minimum cost path from the top-left to the bottom-right +/// corner of a matrix, where movement is restricted to right and down directions. +/// +/// The function calculates the minimum path cost by updating a `cost` vector that +/// stores cumulative path costs for the current row as it iterates through each +/// row of the input `matrix`. +/// +/// # Arguments +/// +/// * `matrix` - A 2D vector of positive integers, where each element represents +/// the cost to step on that cell. +/// +/// # Returns +/// +/// * A single `usize` value representing the minimum path cost to reach the +/// bottom-right corner from the top-left corner of the matrix. +/// +/// # Complexity +/// +/// * Time complexity: `O(m * n)`, where `m` is the number of rows +/// and `n` is the number of columns in the input matrix. +/// * Space complexity: `O(n)`, as only a single row of cumulative costs +/// is stored at any time. +pub fn minimum_cost_path(matrix: Vec>) -> usize { + // Initialize the first row of the cost vector using a scan over the first row of matrix + let mut cost = matrix[0] + .iter() + .scan(0, |acc, &val| { + *acc += val; + Some(*acc) + }) + .collect::>(); - // Updating path cost for the remaining positions - // For each position, cost to reach it from top left is - // Sum of value of that position and minimum of upper and left position value + // Process each row from the second to the last + for row in matrix.iter().skip(1) { + // Update the first element of cost for this row + cost[0] += row[0]; - for i in 1..rows { - for j in 1..columns { - matrix[i][j] += min(matrix[i - 1][j], matrix[i][j - 1]); + // Update the rest of the elements in the current row of cost + for col in 1..matrix[0].len() { + cost[col] = row[col] + min(cost[col - 1], cost[col]); } } - // Return cost for bottom right element - matrix[rows - 1][columns - 1] + // The last element in cost contains the minimum path cost to the bottom-right corner + cost[matrix[0].len() - 1] } #[cfg(test)] mod tests { use super::*; - #[test] - fn basic() { - // For test case in example - let matrix = vec![vec![2, 1, 4], vec![2, 1, 3], vec![3, 2, 1]]; - assert_eq!(minimum_cost_path(matrix), 7); - - // For a randomly generated matrix - let matrix = vec![vec![1, 2, 3], vec![4, 5, 6]]; - assert_eq!(minimum_cost_path(matrix), 12); - } - - #[test] - fn one_element_matrix() { - let matrix = vec![vec![2]]; - assert_eq!(minimum_cost_path(matrix), 2); - } - - #[test] - fn one_row() { - let matrix = vec![vec![1, 3, 2, 1, 5]]; - assert_eq!(minimum_cost_path(matrix), 12); + macro_rules! minimum_cost_path_tests { + ($($name:ident: $test_case:expr,)*) => { + $( + #[test] + fn $name() { + let (matrix, expected) = $test_case; + assert_eq!(minimum_cost_path(matrix), expected); + } + )* + }; } - #[test] - fn one_column() { - let matrix = vec![vec![1], vec![3], vec![2], vec![1], vec![5]]; - assert_eq!(minimum_cost_path(matrix), 12); + minimum_cost_path_tests! { + basic: ( + vec![ + vec![2, 1, 4], + vec![2, 1, 3], + vec![3, 2, 1] + ], + 7 + ), + single_element: ( + vec![ + vec![5] + ], + 5 + ), + single_row: ( + vec![ + vec![1, 3, 2, 1, 5] + ], + 12 + ), + single_column: ( + vec![ + vec![1], + vec![3], + vec![2], + vec![1], + vec![5]], + 12 + ), + large_matrix: ( + vec![ + vec![1, 3, 1, 5], + vec![2, 1, 4, 2], + vec![3, 2, 1, 3], + vec![4, 3, 2, 1] + ], + 10 + ), + uniform_matrix: ( + vec![ + vec![1, 1, 1], + vec![1, 1, 1], + vec![1, 1, 1] + ], + 5 + ), + increasing_values: ( + vec![ + vec![1, 2, 3], + vec![4, 5, 6], + vec![7, 8, 9] + ], + 21 + ), + high_cost_path: ( + vec![ + vec![1, 100, 1], + vec![1, 100, 1], + vec![1, 1, 1] + ], + 5 + ), + complex_matrix: ( + vec![ + vec![5, 9, 6, 8], + vec![1, 4, 7, 3], + vec![2, 1, 8, 2], + vec![3, 6, 9, 4] + ], + 23 + ), } } From a1e8c60eccd6b50da818a7b2ef947360baade8fc Mon Sep 17 00:00:00 2001 From: Truong Nhan Nguyen Date: Sun, 10 Nov 2024 20:33:06 +0700 Subject: [PATCH 2/2] feat: add custom error types --- src/dynamic_programming/minimum_cost_path.rs | 75 +++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/src/dynamic_programming/minimum_cost_path.rs b/src/dynamic_programming/minimum_cost_path.rs index 7f14529e56b..e06481199cf 100644 --- a/src/dynamic_programming/minimum_cost_path.rs +++ b/src/dynamic_programming/minimum_cost_path.rs @@ -1,12 +1,17 @@ use std::cmp::min; +/// Represents possible errors that can occur when calculating the minimum cost path in a matrix. +#[derive(Debug, PartialEq, Eq)] +pub enum MatrixError { + /// Error indicating that the matrix is empty or has empty rows. + EmptyMatrix, + /// Error indicating that the matrix is not rectangular in shape. + NonRectangularMatrix, +} + /// Computes the minimum cost path from the top-left to the bottom-right /// corner of a matrix, where movement is restricted to right and down directions. /// -/// The function calculates the minimum path cost by updating a `cost` vector that -/// stores cumulative path costs for the current row as it iterates through each -/// row of the input `matrix`. -/// /// # Arguments /// /// * `matrix` - A 2D vector of positive integers, where each element represents @@ -14,8 +19,9 @@ use std::cmp::min; /// /// # Returns /// -/// * A single `usize` value representing the minimum path cost to reach the -/// bottom-right corner from the top-left corner of the matrix. +/// * `Ok(usize)` - The minimum path cost to reach the bottom-right corner from +/// the top-left corner of the matrix. +/// * `Err(MatrixError)` - An error if the matrix is empty or improperly formatted. /// /// # Complexity /// @@ -23,8 +29,18 @@ use std::cmp::min; /// and `n` is the number of columns in the input matrix. /// * Space complexity: `O(n)`, as only a single row of cumulative costs /// is stored at any time. -pub fn minimum_cost_path(matrix: Vec>) -> usize { - // Initialize the first row of the cost vector using a scan over the first row of matrix +pub fn minimum_cost_path(matrix: Vec>) -> Result { + // Check if the matrix is rectangular + if !matrix.iter().all(|row| row.len() == matrix[0].len()) { + return Err(MatrixError::NonRectangularMatrix); + } + + // Check if the matrix is empty or contains empty rows + if matrix.is_empty() || matrix.iter().all(|row| row.is_empty()) { + return Err(MatrixError::EmptyMatrix); + } + + // Initialize the first row of the cost vector let mut cost = matrix[0] .iter() .scan(0, |acc, &val| { @@ -45,7 +61,7 @@ pub fn minimum_cost_path(matrix: Vec>) -> usize { } // The last element in cost contains the minimum path cost to the bottom-right corner - cost[matrix[0].len() - 1] + Ok(cost[matrix[0].len() - 1]) } #[cfg(test)] @@ -71,19 +87,19 @@ mod tests { vec![2, 1, 3], vec![3, 2, 1] ], - 7 + Ok(7) ), single_element: ( vec![ vec![5] ], - 5 + Ok(5) ), single_row: ( vec![ vec![1, 3, 2, 1, 5] ], - 12 + Ok(12) ), single_column: ( vec![ @@ -91,8 +107,9 @@ mod tests { vec![3], vec![2], vec![1], - vec![5]], - 12 + vec![5] + ], + Ok(12) ), large_matrix: ( vec![ @@ -101,7 +118,7 @@ mod tests { vec![3, 2, 1, 3], vec![4, 3, 2, 1] ], - 10 + Ok(10) ), uniform_matrix: ( vec![ @@ -109,7 +126,7 @@ mod tests { vec![1, 1, 1], vec![1, 1, 1] ], - 5 + Ok(5) ), increasing_values: ( vec![ @@ -117,7 +134,7 @@ mod tests { vec![4, 5, 6], vec![7, 8, 9] ], - 21 + Ok(21) ), high_cost_path: ( vec![ @@ -125,7 +142,7 @@ mod tests { vec![1, 100, 1], vec![1, 1, 1] ], - 5 + Ok(5) ), complex_matrix: ( vec![ @@ -134,7 +151,27 @@ mod tests { vec![2, 1, 8, 2], vec![3, 6, 9, 4] ], - 23 + Ok(23) + ), + empty_matrix: ( + vec![], + Err(MatrixError::EmptyMatrix) + ), + empty_row: ( + vec![ + vec![], + vec![], + vec![] + ], + Err(MatrixError::EmptyMatrix) + ), + non_rectangular: ( + vec![ + vec![1, 2, 3], + vec![4, 5], + vec![6, 7, 8] + ], + Err(MatrixError::NonRectangularMatrix) ), } }