Skip to content

Commit 685b8b1

Browse files
authored
Merge branch 'master' into ref/ds/segment_tree_recursive
2 parents 95ac923 + 41c76e4 commit 685b8b1

File tree

8 files changed

+444
-180
lines changed

8 files changed

+444
-180
lines changed

.github/workflows/upload_coverage_report.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ jobs:
3131
if: >-
3232
github.event_name == 'pull_request' &&
3333
github.event.pull_request.head.repo.full_name != github.repository
34-
uses: codecov/codecov-action@v4
34+
uses: codecov/codecov-action@v5
3535
with:
3636
files: "${{ env.REPORT_NAME }}"
3737
fail_ci_if_error: true
3838
- name: Upload coverage to codecov (with token)
3939
if: "! github.event.pull_request.head.repo.fork "
40-
uses: codecov/codecov-action@v4
40+
uses: codecov/codecov-action@v5
4141
with:
4242
token: ${{ secrets.CODECOV_TOKEN }}
4343
files: "${{ env.REPORT_NAME }}"

DIRECTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
* [Subset Generation](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/subset_generation.rs)
102102
* [Trapped Rainwater](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/trapped_rainwater.rs)
103103
* [Word Break](https://github.com/TheAlgorithms/Rust/blob/master/src/dynamic_programming/word_break.rs)
104+
* Financial
105+
* [Present Value](https://github.com/TheAlgorithms/Rust/blob/master/src/financial/present_value.rs)
104106
* General
105107
* [Convex Hull](https://github.com/TheAlgorithms/Rust/blob/master/src/general/convex_hull.rs)
106108
* [Fisher Yates Shuffle](https://github.com/TheAlgorithms/Rust/blob/master/src/general/fisher_yates_shuffle.rs)
Lines changed: 156 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,177 @@
1-
/// Minimum Cost Path via Dynamic Programming
2-
3-
/// Find the minimum cost traced by all possible paths from top left to bottom right in
4-
/// a given matrix, by allowing only right and down movement
5-
6-
/// For example, in matrix,
7-
/// [2, 1, 4]
8-
/// [2, 1, 3]
9-
/// [3, 2, 1]
10-
/// The minimum cost path is 7
11-
12-
/// # Arguments:
13-
/// * `matrix` - The input matrix.
14-
/// # Complexity
15-
/// - time complexity: O( rows * columns ),
16-
/// - space complexity: O( rows * columns )
171
use std::cmp::min;
182

19-
pub fn minimum_cost_path(mut matrix: Vec<Vec<usize>>) -> usize {
20-
// Add rows and columns variables for better readability
21-
let rows = matrix.len();
22-
let columns = matrix[0].len();
3+
/// Represents possible errors that can occur when calculating the minimum cost path in a matrix.
4+
#[derive(Debug, PartialEq, Eq)]
5+
pub enum MatrixError {
6+
/// Error indicating that the matrix is empty or has empty rows.
7+
EmptyMatrix,
8+
/// Error indicating that the matrix is not rectangular in shape.
9+
NonRectangularMatrix,
10+
}
2311

24-
// Preprocessing the first row
25-
for i in 1..columns {
26-
matrix[0][i] += matrix[0][i - 1];
12+
/// Computes the minimum cost path from the top-left to the bottom-right
13+
/// corner of a matrix, where movement is restricted to right and down directions.
14+
///
15+
/// # Arguments
16+
///
17+
/// * `matrix` - A 2D vector of positive integers, where each element represents
18+
/// the cost to step on that cell.
19+
///
20+
/// # Returns
21+
///
22+
/// * `Ok(usize)` - The minimum path cost to reach the bottom-right corner from
23+
/// the top-left corner of the matrix.
24+
/// * `Err(MatrixError)` - An error if the matrix is empty or improperly formatted.
25+
///
26+
/// # Complexity
27+
///
28+
/// * Time complexity: `O(m * n)`, where `m` is the number of rows
29+
/// and `n` is the number of columns in the input matrix.
30+
/// * Space complexity: `O(n)`, as only a single row of cumulative costs
31+
/// is stored at any time.
32+
pub fn minimum_cost_path(matrix: Vec<Vec<usize>>) -> Result<usize, MatrixError> {
33+
// Check if the matrix is rectangular
34+
if !matrix.iter().all(|row| row.len() == matrix[0].len()) {
35+
return Err(MatrixError::NonRectangularMatrix);
2736
}
2837

29-
// Preprocessing the first column
30-
for i in 1..rows {
31-
matrix[i][0] += matrix[i - 1][0];
38+
// Check if the matrix is empty or contains empty rows
39+
if matrix.is_empty() || matrix.iter().all(|row| row.is_empty()) {
40+
return Err(MatrixError::EmptyMatrix);
3241
}
3342

34-
// Updating path cost for the remaining positions
35-
// For each position, cost to reach it from top left is
36-
// Sum of value of that position and minimum of upper and left position value
43+
// Initialize the first row of the cost vector
44+
let mut cost = matrix[0]
45+
.iter()
46+
.scan(0, |acc, &val| {
47+
*acc += val;
48+
Some(*acc)
49+
})
50+
.collect::<Vec<_>>();
3751

38-
for i in 1..rows {
39-
for j in 1..columns {
40-
matrix[i][j] += min(matrix[i - 1][j], matrix[i][j - 1]);
52+
// Process each row from the second to the last
53+
for row in matrix.iter().skip(1) {
54+
// Update the first element of cost for this row
55+
cost[0] += row[0];
56+
57+
// Update the rest of the elements in the current row of cost
58+
for col in 1..matrix[0].len() {
59+
cost[col] = row[col] + min(cost[col - 1], cost[col]);
4160
}
4261
}
4362

44-
// Return cost for bottom right element
45-
matrix[rows - 1][columns - 1]
63+
// The last element in cost contains the minimum path cost to the bottom-right corner
64+
Ok(cost[matrix[0].len() - 1])
4665
}
4766

4867
#[cfg(test)]
4968
mod tests {
5069
use super::*;
5170

52-
#[test]
53-
fn basic() {
54-
// For test case in example
55-
let matrix = vec![vec![2, 1, 4], vec![2, 1, 3], vec![3, 2, 1]];
56-
assert_eq!(minimum_cost_path(matrix), 7);
57-
58-
// For a randomly generated matrix
59-
let matrix = vec![vec![1, 2, 3], vec![4, 5, 6]];
60-
assert_eq!(minimum_cost_path(matrix), 12);
61-
}
62-
63-
#[test]
64-
fn one_element_matrix() {
65-
let matrix = vec![vec![2]];
66-
assert_eq!(minimum_cost_path(matrix), 2);
67-
}
68-
69-
#[test]
70-
fn one_row() {
71-
let matrix = vec![vec![1, 3, 2, 1, 5]];
72-
assert_eq!(minimum_cost_path(matrix), 12);
71+
macro_rules! minimum_cost_path_tests {
72+
($($name:ident: $test_case:expr,)*) => {
73+
$(
74+
#[test]
75+
fn $name() {
76+
let (matrix, expected) = $test_case;
77+
assert_eq!(minimum_cost_path(matrix), expected);
78+
}
79+
)*
80+
};
7381
}
7482

75-
#[test]
76-
fn one_column() {
77-
let matrix = vec![vec![1], vec![3], vec![2], vec![1], vec![5]];
78-
assert_eq!(minimum_cost_path(matrix), 12);
83+
minimum_cost_path_tests! {
84+
basic: (
85+
vec![
86+
vec![2, 1, 4],
87+
vec![2, 1, 3],
88+
vec![3, 2, 1]
89+
],
90+
Ok(7)
91+
),
92+
single_element: (
93+
vec![
94+
vec![5]
95+
],
96+
Ok(5)
97+
),
98+
single_row: (
99+
vec![
100+
vec![1, 3, 2, 1, 5]
101+
],
102+
Ok(12)
103+
),
104+
single_column: (
105+
vec![
106+
vec![1],
107+
vec![3],
108+
vec![2],
109+
vec![1],
110+
vec![5]
111+
],
112+
Ok(12)
113+
),
114+
large_matrix: (
115+
vec![
116+
vec![1, 3, 1, 5],
117+
vec![2, 1, 4, 2],
118+
vec![3, 2, 1, 3],
119+
vec![4, 3, 2, 1]
120+
],
121+
Ok(10)
122+
),
123+
uniform_matrix: (
124+
vec![
125+
vec![1, 1, 1],
126+
vec![1, 1, 1],
127+
vec![1, 1, 1]
128+
],
129+
Ok(5)
130+
),
131+
increasing_values: (
132+
vec![
133+
vec![1, 2, 3],
134+
vec![4, 5, 6],
135+
vec![7, 8, 9]
136+
],
137+
Ok(21)
138+
),
139+
high_cost_path: (
140+
vec![
141+
vec![1, 100, 1],
142+
vec![1, 100, 1],
143+
vec![1, 1, 1]
144+
],
145+
Ok(5)
146+
),
147+
complex_matrix: (
148+
vec![
149+
vec![5, 9, 6, 8],
150+
vec![1, 4, 7, 3],
151+
vec![2, 1, 8, 2],
152+
vec![3, 6, 9, 4]
153+
],
154+
Ok(23)
155+
),
156+
empty_matrix: (
157+
vec![],
158+
Err(MatrixError::EmptyMatrix)
159+
),
160+
empty_row: (
161+
vec![
162+
vec![],
163+
vec![],
164+
vec![]
165+
],
166+
Err(MatrixError::EmptyMatrix)
167+
),
168+
non_rectangular: (
169+
vec![
170+
vec![1, 2, 3],
171+
vec![4, 5],
172+
vec![6, 7, 8]
173+
],
174+
Err(MatrixError::NonRectangularMatrix)
175+
),
79176
}
80177
}

src/financial/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod present_value;
2+
pub use present_value::present_value;

src/financial/present_value.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/// In economics and finance, present value (PV), also known as present discounted value,
2+
/// is the value of an expected income stream determined as of the date of valuation.
3+
///
4+
/// -> Wikipedia reference: https://en.wikipedia.org/wiki/Present_value
5+
6+
#[derive(PartialEq, Eq, Debug)]
7+
pub enum PresentValueError {
8+
NegetiveDiscount,
9+
EmptyCashFlow,
10+
}
11+
12+
pub fn present_value(discount_rate: f64, cash_flows: Vec<f64>) -> Result<f64, PresentValueError> {
13+
if discount_rate < 0.0 {
14+
return Err(PresentValueError::NegetiveDiscount);
15+
}
16+
if cash_flows.is_empty() {
17+
return Err(PresentValueError::EmptyCashFlow);
18+
}
19+
20+
let present_value = cash_flows
21+
.iter()
22+
.enumerate()
23+
.map(|(i, &cash_flow)| cash_flow / (1.0 + discount_rate).powi(i as i32))
24+
.sum::<f64>();
25+
26+
Ok(round(present_value))
27+
}
28+
29+
fn round(value: f64) -> f64 {
30+
(value * 100.0).round() / 100.0
31+
}
32+
33+
#[cfg(test)]
34+
mod tests {
35+
use super::*;
36+
37+
macro_rules! test_present_value {
38+
($($name:ident: $inputs:expr,)*) => {
39+
$(
40+
#[test]
41+
fn $name() {
42+
let ((discount_rate,cash_flows), expected) = $inputs;
43+
assert_eq!(present_value(discount_rate,cash_flows).unwrap(), expected);
44+
}
45+
)*
46+
}
47+
}
48+
49+
macro_rules! test_present_value_Err {
50+
($($name:ident: $inputs:expr,)*) => {
51+
$(
52+
#[test]
53+
fn $name() {
54+
let ((discount_rate,cash_flows), expected) = $inputs;
55+
assert_eq!(present_value(discount_rate,cash_flows).unwrap_err(), expected);
56+
}
57+
)*
58+
}
59+
}
60+
61+
macro_rules! test_round {
62+
($($name:ident: $inputs:expr,)*) => {
63+
$(
64+
#[test]
65+
fn $name() {
66+
let (input, expected) = $inputs;
67+
assert_eq!(round(input), expected);
68+
}
69+
)*
70+
}
71+
}
72+
73+
test_present_value! {
74+
general_inputs1:((0.13, vec![10.0, 20.70, -293.0, 297.0]),4.69),
75+
general_inputs2:((0.07, vec![-109129.39, 30923.23, 15098.93, 29734.0, 39.0]),-42739.63),
76+
general_inputs3:((0.07, vec![109129.39, 30923.23, 15098.93, 29734.0, 39.0]), 175519.15),
77+
zero_input:((0.0, vec![109129.39, 30923.23, 15098.93, 29734.0, 39.0]), 184924.55),
78+
79+
}
80+
81+
test_present_value_Err! {
82+
negative_discount_rate:((-1.0, vec![10.0, 20.70, -293.0, 297.0]), PresentValueError::NegetiveDiscount),
83+
empty_cash_flow:((1.0, vec![]), PresentValueError::EmptyCashFlow),
84+
85+
}
86+
test_round! {
87+
test1:(0.55434, 0.55),
88+
test2:(10.453, 10.45),
89+
test3:(1111_f64, 1111_f64),
90+
}
91+
}

0 commit comments

Comments
 (0)