Skip to content

Commit 28dda98

Browse files
sozelfistvil02
andauthored
Improve All Combinations of Size k Implementation (#782)
* ref: refactor all combinations of size k implementation * ref: add `CombinationError` type * ref: refactor implementation - The recursion starts from `start = 0`, so it aligns with zero-indexing. - Pre-allocate `current` vector and using index to track position to avoid unnecessary heap allocations during each recursive call --------- Co-authored-by: Piotr Idzik <[email protected]>
1 parent 44270b7 commit 28dda98

File tree

1 file changed

+105
-44
lines changed

1 file changed

+105
-44
lines changed
Lines changed: 105 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,123 @@
1-
/*
2-
In this problem, we want to determine all possible combinations of k
3-
numbers out of 1 ... n. We use backtracking to solve this problem.
4-
Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!)))
5-
6-
generate_all_combinations(n=4, k=2) => [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
7-
*/
8-
pub fn generate_all_combinations(n: i32, k: i32) -> Vec<Vec<i32>> {
9-
let mut result = vec![];
10-
create_all_state(1, n, k, &mut vec![], &mut result);
11-
12-
result
1+
//! This module provides a function to generate all possible combinations
2+
//! of `k` numbers out of `0...n-1` using a backtracking algorithm.
3+
4+
/// Custom error type for combination generation.
5+
#[derive(Debug, PartialEq)]
6+
pub enum CombinationError {
7+
KGreaterThanN,
8+
InvalidZeroRange,
9+
}
10+
11+
/// Generates all possible combinations of `k` numbers out of `0...n-1`.
12+
///
13+
/// # Arguments
14+
///
15+
/// * `n` - The upper limit of the range (`0` to `n-1`).
16+
/// * `k` - The number of elements in each combination.
17+
///
18+
/// # Returns
19+
///
20+
/// A `Result` containing a vector with all possible combinations of `k` numbers out of `0...n-1`,
21+
/// or a `CombinationError` if the input is invalid.
22+
pub fn generate_all_combinations(n: usize, k: usize) -> Result<Vec<Vec<usize>>, CombinationError> {
23+
if n == 0 && k > 0 {
24+
return Err(CombinationError::InvalidZeroRange);
25+
}
26+
27+
if k > n {
28+
return Err(CombinationError::KGreaterThanN);
29+
}
30+
31+
let mut combinations = vec![];
32+
let mut current = vec![0; k];
33+
backtrack(0, n, k, 0, &mut current, &mut combinations);
34+
Ok(combinations)
1335
}
1436

15-
fn create_all_state(
16-
increment: i32,
17-
total_number: i32,
18-
level: i32,
19-
current_list: &mut Vec<i32>,
20-
total_list: &mut Vec<Vec<i32>>,
37+
/// Helper function to generate combinations recursively.
38+
///
39+
/// # Arguments
40+
///
41+
/// * `start` - The current number to start the combination with.
42+
/// * `n` - The upper limit of the range (`0` to `n-1`).
43+
/// * `k` - The number of elements left to complete the combination.
44+
/// * `index` - The current index being filled in the combination.
45+
/// * `current` - A mutable reference to the current combination being constructed.
46+
/// * `combinations` - A mutable reference to the vector holding all combinations.
47+
fn backtrack(
48+
start: usize,
49+
n: usize,
50+
k: usize,
51+
index: usize,
52+
current: &mut Vec<usize>,
53+
combinations: &mut Vec<Vec<usize>>,
2154
) {
22-
if level == 0 {
23-
total_list.push(current_list.clone());
55+
if index == k {
56+
combinations.push(current.clone());
2457
return;
2558
}
2659

27-
for i in increment..(total_number - level + 2) {
28-
current_list.push(i);
29-
create_all_state(i + 1, total_number, level - 1, current_list, total_list);
30-
current_list.pop();
60+
for num in start..=(n - k + index) {
61+
current[index] = num;
62+
backtrack(num + 1, n, k, index + 1, current, combinations);
3163
}
3264
}
3365

3466
#[cfg(test)]
3567
mod tests {
3668
use super::*;
3769

38-
#[test]
39-
fn test_output() {
40-
let expected_res = vec![
70+
macro_rules! combination_tests {
71+
($($name:ident: $test_case:expr,)*) => {
72+
$(
73+
#[test]
74+
fn $name() {
75+
let (n, k, expected) = $test_case;
76+
assert_eq!(generate_all_combinations(n, k), expected);
77+
}
78+
)*
79+
}
80+
}
81+
82+
combination_tests! {
83+
test_generate_4_2: (4, 2, Ok(vec![
84+
vec![0, 1],
85+
vec![0, 2],
86+
vec![0, 3],
4187
vec![1, 2],
4288
vec![1, 3],
43-
vec![1, 4],
4489
vec![2, 3],
45-
vec![2, 4],
46-
vec![3, 4],
47-
];
48-
49-
let res = generate_all_combinations(4, 2);
50-
51-
assert_eq!(expected_res, res);
52-
}
53-
54-
#[test]
55-
fn test_empty() {
56-
let expected_res: Vec<Vec<i32>> = vec![vec![]];
57-
58-
let res = generate_all_combinations(0, 0);
59-
60-
assert_eq!(expected_res, res);
90+
])),
91+
test_generate_4_3: (4, 3, Ok(vec![
92+
vec![0, 1, 2],
93+
vec![0, 1, 3],
94+
vec![0, 2, 3],
95+
vec![1, 2, 3],
96+
])),
97+
test_generate_5_3: (5, 3, Ok(vec![
98+
vec![0, 1, 2],
99+
vec![0, 1, 3],
100+
vec![0, 1, 4],
101+
vec![0, 2, 3],
102+
vec![0, 2, 4],
103+
vec![0, 3, 4],
104+
vec![1, 2, 3],
105+
vec![1, 2, 4],
106+
vec![1, 3, 4],
107+
vec![2, 3, 4],
108+
])),
109+
test_generate_5_1: (5, 1, Ok(vec![
110+
vec![0],
111+
vec![1],
112+
vec![2],
113+
vec![3],
114+
vec![4],
115+
])),
116+
test_empty: (0, 0, Ok(vec![vec![]])),
117+
test_generate_n_eq_k: (3, 3, Ok(vec![
118+
vec![0, 1, 2],
119+
])),
120+
test_generate_k_greater_than_n: (3, 4, Err(CombinationError::KGreaterThanN)),
121+
test_zero_range_with_nonzero_k: (0, 1, Err(CombinationError::InvalidZeroRange)),
61122
}
62123
}

0 commit comments

Comments
 (0)