Skip to content

Commit b705fd7

Browse files
feat: add Minimum Coin Change greedy algorithm (#992)
1 parent e4c9e6b commit b705fd7

File tree

3 files changed

+281
-0
lines changed

3 files changed

+281
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@
195195
* [Topological Sort](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/topological_sort.rs)
196196
* [Two Satisfiability](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/two_satisfiability.rs)
197197
* Greedy
198+
* [Minimum Coin Change](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/minimum_coin_changes.rs)
198199
* [Stable Matching](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/stable_matching.rs)
199200
* [Lib](https://github.com/TheAlgorithms/Rust/blob/master/src/lib.rs)
200201
* Machine Learning

src/greedy/minimum_coin_change.rs

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
//! # Minimum Coin Change (Greedy Algorithm)
2+
//!
3+
//! This module implements a greedy algorithm to find the minimum number of coins
4+
//! needed to make change for a given amount using specified denominations.
5+
//!
6+
//! ## Algorithm
7+
//!
8+
//! The greedy approach works by always selecting the largest denomination possible
9+
//! at each step. While this approach doesn't guarantee an optimal solution for all
10+
//! denomination systems, it works correctly for canonical coin systems (like most
11+
//! real-world currencies including USD, EUR, INR, etc.).
12+
//!
13+
//! ## Time Complexity
14+
//!
15+
//! O(n) where n is the number of denominations
16+
//!
17+
//! ## Space Complexity
18+
//!
19+
//! O(m) where m is the number of coins in the result
20+
//!
21+
//! ## Example
22+
//!
23+
//! ```
24+
//! # fn find_minimum_change(denominations: &[i32], value: i32) -> Vec<i32> {
25+
//! # if value <= 0 || denominations.is_empty() {
26+
//! # return Vec::new();
27+
//! # }
28+
//! # let mut remaining_value = value;
29+
//! # let mut result = Vec::new();
30+
//! # let mut sorted_denominations = denominations.to_vec();
31+
//! # sorted_denominations.sort_unstable_by(|a, b| b.cmp(a));
32+
//! # for &denomination in &sorted_denominations {
33+
//! # while remaining_value >= denomination {
34+
//! # remaining_value -= denomination;
35+
//! # result.push(denomination);
36+
//! # }
37+
//! # }
38+
//! # result
39+
//! # }
40+
//! let denominations = vec![1, 2, 5, 10, 20, 50, 100, 500, 2000];
41+
//! let result = find_minimum_change(&denominations, 987);
42+
//! assert_eq!(result, vec![500, 100, 100, 100, 100, 50, 20, 10, 5, 2]);
43+
//! ```
44+
45+
/// Finds the minimum number of coins needed to make change for a given value
46+
/// using a greedy algorithm.
47+
///
48+
/// # Arguments
49+
///
50+
/// * `denominations` - A slice of available coin denominations (must be positive integers)
51+
/// * `value` - The target value to make change for (must be non-negative)
52+
///
53+
/// # Returns
54+
///
55+
/// A vector containing the coins used, in descending order. Returns an empty vector
56+
/// if the value is zero or negative, or if denominations is empty.
57+
///
58+
/// # Examples
59+
///
60+
/// ```
61+
/// # fn find_minimum_change(denominations: &[i32], value: i32) -> Vec<i32> {
62+
/// # if value <= 0 || denominations.is_empty() { return Vec::new(); }
63+
/// # let mut remaining_value = value;
64+
/// # let mut result = Vec::new();
65+
/// # let mut sorted_denominations = denominations.to_vec();
66+
/// # sorted_denominations.sort_unstable_by(|a, b| b.cmp(a));
67+
/// # for &denomination in &sorted_denominations {
68+
/// # while remaining_value >= denomination {
69+
/// # remaining_value -= denomination;
70+
/// # result.push(denomination);
71+
/// # }
72+
/// # }
73+
/// # result
74+
/// # }
75+
/// // Indian currency example
76+
/// let denominations = vec![1, 2, 5, 10, 20, 50, 100, 500, 2000];
77+
/// let result = find_minimum_change(&denominations, 987);
78+
/// assert_eq!(result, vec![500, 100, 100, 100, 100, 50, 20, 10, 5, 2]);
79+
/// ```
80+
///
81+
/// ```
82+
/// # fn find_minimum_change(denominations: &[i32], value: i32) -> Vec<i32> {
83+
/// # if value <= 0 || denominations.is_empty() { return Vec::new(); }
84+
/// # let mut remaining_value = value;
85+
/// # let mut result = Vec::new();
86+
/// # let mut sorted_denominations = denominations.to_vec();
87+
/// # sorted_denominations.sort_unstable_by(|a, b| b.cmp(a));
88+
/// # for &denomination in &sorted_denominations {
89+
/// # while remaining_value >= denomination {
90+
/// # remaining_value -= denomination;
91+
/// # result.push(denomination);
92+
/// # }
93+
/// # }
94+
/// # result
95+
/// # }
96+
/// // Large amount example
97+
/// let denominations = vec![1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000];
98+
/// let result = find_minimum_change(&denominations, 18745);
99+
/// assert_eq!(
100+
/// result,
101+
/// vec![2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 500, 200, 20, 20, 5]
102+
/// );
103+
/// ```
104+
///
105+
/// ```
106+
/// # fn find_minimum_change(denominations: &[i32], value: i32) -> Vec<i32> {
107+
/// # if value <= 0 || denominations.is_empty() { return Vec::new(); }
108+
/// # let mut remaining_value = value;
109+
/// # let mut result = Vec::new();
110+
/// # let mut sorted_denominations = denominations.to_vec();
111+
/// # sorted_denominations.sort_unstable_by(|a, b| b.cmp(a));
112+
/// # for &denomination in &sorted_denominations {
113+
/// # while remaining_value >= denomination {
114+
/// # remaining_value -= denomination;
115+
/// # result.push(denomination);
116+
/// # }
117+
/// # }
118+
/// # result
119+
/// # }
120+
/// // Edge case: zero value
121+
/// let denominations = vec![1, 2, 5, 10];
122+
/// let result = find_minimum_change(&denominations, 0);
123+
/// assert_eq!(result, Vec::<i32>::new());
124+
/// ```
125+
///
126+
/// ```
127+
/// # fn find_minimum_change(denominations: &[i32], value: i32) -> Vec<i32> {
128+
/// # if value <= 0 || denominations.is_empty() { return Vec::new(); }
129+
/// # let mut remaining_value = value;
130+
/// # let mut result = Vec::new();
131+
/// # let mut sorted_denominations = denominations.to_vec();
132+
/// # sorted_denominations.sort_unstable_by(|a, b| b.cmp(a));
133+
/// # for &denomination in &sorted_denominations {
134+
/// # while remaining_value >= denomination {
135+
/// # remaining_value -= denomination;
136+
/// # result.push(denomination);
137+
/// # }
138+
/// # }
139+
/// # result
140+
/// # }
141+
/// // Edge case: negative value
142+
/// let denominations = vec![1, 2, 5, 10];
143+
/// let result = find_minimum_change(&denominations, -50);
144+
/// assert_eq!(result, Vec::<i32>::new());
145+
/// ```
146+
///
147+
/// ```
148+
/// # fn find_minimum_change(denominations: &[i32], value: i32) -> Vec<i32> {
149+
/// # if value <= 0 || denominations.is_empty() { return Vec::new(); }
150+
/// # let mut remaining_value = value;
151+
/// # let mut result = Vec::new();
152+
/// # let mut sorted_denominations = denominations.to_vec();
153+
/// # sorted_denominations.sort_unstable_by(|a, b| b.cmp(a));
154+
/// # for &denomination in &sorted_denominations {
155+
/// # while remaining_value >= denomination {
156+
/// # remaining_value -= denomination;
157+
/// # result.push(denomination);
158+
/// # }
159+
/// # }
160+
/// # result
161+
/// # }
162+
/// // Non-standard denominations
163+
/// let denominations = vec![1, 5, 100, 500, 1000];
164+
/// let result = find_minimum_change(&denominations, 456);
165+
/// assert_eq!(
166+
/// result,
167+
/// vec![100, 100, 100, 100, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1]
168+
/// );
169+
/// ```
170+
pub fn find_minimum_change(denominations: &[i32], value: i32) -> Vec<i32> {
171+
// Handle edge cases
172+
if value <= 0 || denominations.is_empty() {
173+
return Vec::new();
174+
}
175+
176+
let mut remaining_value = value;
177+
let mut result = Vec::new();
178+
179+
// Sort denominations in descending order for greedy selection
180+
let mut sorted_denominations = denominations.to_vec();
181+
sorted_denominations.sort_unstable_by(|a, b| b.cmp(a));
182+
183+
// Greedily select the largest denomination at each step
184+
for &denomination in &sorted_denominations {
185+
while remaining_value >= denomination {
186+
remaining_value -= denomination;
187+
result.push(denomination);
188+
}
189+
}
190+
191+
result
192+
}
193+
194+
#[cfg(test)]
195+
mod tests {
196+
use super::*;
197+
198+
#[test]
199+
fn test_indian_currency_standard() {
200+
let denominations = vec![1, 2, 5, 10, 20, 50, 100, 500, 2000];
201+
let result = find_minimum_change(&denominations, 987);
202+
assert_eq!(result, vec![500, 100, 100, 100, 100, 50, 20, 10, 5, 2]);
203+
assert_eq!(result.len(), 10);
204+
}
205+
206+
#[test]
207+
fn test_large_amount() {
208+
let denominations = vec![1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000];
209+
let result = find_minimum_change(&denominations, 18745);
210+
assert_eq!(
211+
result,
212+
vec![2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 500, 200, 20, 20, 5]
213+
);
214+
assert_eq!(result.iter().sum::<i32>(), 18745);
215+
}
216+
217+
#[test]
218+
fn test_zero_value() {
219+
let denominations = vec![1, 2, 5, 10, 20, 50, 100, 500, 2000];
220+
let result = find_minimum_change(&denominations, 0);
221+
assert_eq!(result, Vec::<i32>::new());
222+
}
223+
224+
#[test]
225+
fn test_negative_value() {
226+
let denominations = vec![1, 2, 5, 10, 20, 50, 100, 500, 2000];
227+
let result = find_minimum_change(&denominations, -98);
228+
assert_eq!(result, Vec::<i32>::new());
229+
}
230+
231+
#[test]
232+
fn test_non_standard_denominations() {
233+
let denominations = vec![1, 5, 100, 500, 1000];
234+
let result = find_minimum_change(&denominations, 456);
235+
assert_eq!(
236+
result,
237+
vec![100, 100, 100, 100, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1]
238+
);
239+
assert_eq!(result.iter().sum::<i32>(), 456);
240+
}
241+
242+
#[test]
243+
fn test_single_denomination() {
244+
let denominations = vec![5];
245+
let result = find_minimum_change(&denominations, 25);
246+
assert_eq!(result, vec![5, 5, 5, 5, 5]);
247+
}
248+
249+
#[test]
250+
fn test_exact_denomination() {
251+
let denominations = vec![1, 5, 10, 25, 50, 100];
252+
let result = find_minimum_change(&denominations, 100);
253+
assert_eq!(result, vec![100]);
254+
}
255+
256+
#[test]
257+
fn test_empty_denominations() {
258+
let denominations: Vec<i32> = vec![];
259+
let result = find_minimum_change(&denominations, 100);
260+
assert_eq!(result, Vec::<i32>::new());
261+
}
262+
263+
#[test]
264+
fn test_unsorted_denominations() {
265+
let denominations = vec![100, 1, 50, 5, 20, 10, 2];
266+
let result = find_minimum_change(&denominations, 178);
267+
assert_eq!(result, vec![100, 50, 20, 5, 2, 1]);
268+
assert_eq!(result.iter().sum::<i32>(), 178);
269+
}
270+
271+
#[test]
272+
fn test_usd_currency() {
273+
let denominations = vec![1, 5, 10, 25, 50, 100]; // cents
274+
let result = find_minimum_change(&denominations, 99);
275+
assert_eq!(result, vec![50, 25, 10, 10, 1, 1, 1, 1]);
276+
assert_eq!(result.len(), 8);
277+
}
278+
}

src/greedy/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod minimum_coin_change;
12
mod stable_matching;
23

4+
pub use self::minimum_coin_change::find_minimum_change;
35
pub use self::stable_matching::stable_matching;

0 commit comments

Comments
 (0)