Skip to content

Commit

Permalink
Merge branch 'master' into ref/ds/rmq
Browse files Browse the repository at this point in the history
  • Loading branch information
sozelfist authored Nov 15, 2024
2 parents d78df79 + ac0b333 commit ddb2d6e
Show file tree
Hide file tree
Showing 7 changed files with 442 additions and 178 deletions.
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@
* [Subset Generation](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/subset_generation.rs)
* [Trapped Rainwater](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)
* Financial
* [Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/present_value.rs)
* General
* [Convex Hull](https://github.com/TheAlgorithms/Rust/blob/master/src/general/convex_hull.rs)
* [Fisher Yates Shuffle](https://github.com/TheAlgorithms/Rust/blob/master/src/general/fisher_yates_shuffle.rs)
Expand Down
215 changes: 156 additions & 59 deletions src/dynamic_programming/minimum_cost_path.rs
Original file line number Diff line number Diff line change
@@ -1,80 +1,177 @@
/// 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<Vec<usize>>) -> usize {
// Add rows and columns variables for better readability
let rows = matrix.len();
let columns = matrix[0].len();
/// 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,
}

// Preprocessing the first row
for i in 1..columns {
matrix[0][i] += matrix[0][i - 1];
/// 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.
///
/// # Arguments
///
/// * `matrix` - A 2D vector of positive integers, where each element represents
/// the cost to step on that cell.
///
/// # Returns
///
/// * `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
///
/// * 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<Vec<usize>>) -> Result<usize, MatrixError> {
// Check if the matrix is rectangular
if !matrix.iter().all(|row| row.len() == matrix[0].len()) {
return Err(MatrixError::NonRectangularMatrix);
}

// Preprocessing the first column
for i in 1..rows {
matrix[i][0] += matrix[i - 1][0];
// 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);
}

// 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
// Initialize the first row of the cost vector
let mut cost = matrix[0]
.iter()
.scan(0, |acc, &val| {
*acc += val;
Some(*acc)
})
.collect::<Vec<_>>();

for i in 1..rows {
for j in 1..columns {
matrix[i][j] += min(matrix[i - 1][j], matrix[i][j - 1]);
// 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];

// 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
Ok(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]
],
Ok(7)
),
single_element: (
vec![
vec![5]
],
Ok(5)
),
single_row: (
vec![
vec![1, 3, 2, 1, 5]
],
Ok(12)
),
single_column: (
vec![
vec![1],
vec![3],
vec![2],
vec![1],
vec![5]
],
Ok(12)
),
large_matrix: (
vec![
vec![1, 3, 1, 5],
vec![2, 1, 4, 2],
vec![3, 2, 1, 3],
vec![4, 3, 2, 1]
],
Ok(10)
),
uniform_matrix: (
vec![
vec![1, 1, 1],
vec![1, 1, 1],
vec![1, 1, 1]
],
Ok(5)
),
increasing_values: (
vec![
vec![1, 2, 3],
vec![4, 5, 6],
vec![7, 8, 9]
],
Ok(21)
),
high_cost_path: (
vec![
vec![1, 100, 1],
vec![1, 100, 1],
vec![1, 1, 1]
],
Ok(5)
),
complex_matrix: (
vec![
vec![5, 9, 6, 8],
vec![1, 4, 7, 3],
vec![2, 1, 8, 2],
vec![3, 6, 9, 4]
],
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)
),
}
}
2 changes: 2 additions & 0 deletions src/financial/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod present_value;
pub use present_value::present_value;
91 changes: 91 additions & 0 deletions src/financial/present_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/// In economics and finance, present value (PV), also known as present discounted value,
/// is the value of an expected income stream determined as of the date of valuation.
///
/// -> Wikipedia reference: https://en.wikipedia.org/wiki/Present_value

#[derive(PartialEq, Eq, Debug)]
pub enum PresentValueError {
NegetiveDiscount,
EmptyCashFlow,
}

pub fn present_value(discount_rate: f64, cash_flows: Vec<f64>) -> Result<f64, PresentValueError> {
if discount_rate < 0.0 {
return Err(PresentValueError::NegetiveDiscount);
}
if cash_flows.is_empty() {
return Err(PresentValueError::EmptyCashFlow);
}

let present_value = cash_flows
.iter()
.enumerate()
.map(|(i, &cash_flow)| cash_flow / (1.0 + discount_rate).powi(i as i32))
.sum::<f64>();

Ok(round(present_value))
}

fn round(value: f64) -> f64 {
(value * 100.0).round() / 100.0
}

#[cfg(test)]
mod tests {
use super::*;

macro_rules! test_present_value {
($($name:ident: $inputs:expr,)*) => {
$(
#[test]
fn $name() {
let ((discount_rate,cash_flows), expected) = $inputs;
assert_eq!(present_value(discount_rate,cash_flows).unwrap(), expected);
}
)*
}
}

macro_rules! test_present_value_Err {
($($name:ident: $inputs:expr,)*) => {
$(
#[test]
fn $name() {
let ((discount_rate,cash_flows), expected) = $inputs;
assert_eq!(present_value(discount_rate,cash_flows).unwrap_err(), expected);
}
)*
}
}

macro_rules! test_round {
($($name:ident: $inputs:expr,)*) => {
$(
#[test]
fn $name() {
let (input, expected) = $inputs;
assert_eq!(round(input), expected);
}
)*
}
}

test_present_value! {
general_inputs1:((0.13, vec![10.0, 20.70, -293.0, 297.0]),4.69),
general_inputs2:((0.07, vec![-109129.39, 30923.23, 15098.93, 29734.0, 39.0]),-42739.63),
general_inputs3:((0.07, vec![109129.39, 30923.23, 15098.93, 29734.0, 39.0]), 175519.15),
zero_input:((0.0, vec![109129.39, 30923.23, 15098.93, 29734.0, 39.0]), 184924.55),

}

test_present_value_Err! {
negative_discount_rate:((-1.0, vec![10.0, 20.70, -293.0, 297.0]), PresentValueError::NegetiveDiscount),
empty_cash_flow:((1.0, vec![]), PresentValueError::EmptyCashFlow),

}
test_round! {
test1:(0.55434, 0.55),
test2:(10.453, 10.45),
test3:(1111_f64, 1111_f64),
}
}
Loading

0 comments on commit ddb2d6e

Please sign in to comment.