use crate::count::binomial;
use crate::error::CombError;
use num_bigint::BigUint;
#[derive(Debug, Clone)]
pub struct CombinationIter {
n: usize,
k: usize,
current: Option<Vec<usize>>,
}
pub fn combinations(n: usize, k: usize) -> Result<CombinationIter, CombError> {
if k > n {
return Err(CombError::InvalidParameters(format!(
"combinations: k={k} > n={n}"
)));
}
Ok(CombinationIter {
n,
k,
current: Some((0..k).collect()),
})
}
impl Iterator for CombinationIter {
type Item = Vec<usize>;
fn next(&mut self) -> Option<Self::Item> {
let cur = self.current.clone()?;
let mut next = cur.clone();
let mut i = self.k;
let advanced = loop {
if i == 0 {
break false;
}
i -= 1;
if next[i] < self.n - self.k + i {
next[i] += 1;
for j in (i + 1)..self.k {
next[j] = next[j - 1] + 1;
}
break true;
}
};
self.current = if advanced { Some(next) } else { None };
Some(cur)
}
}
fn validate(combo: &[usize], n: usize) -> Result<(), CombError> {
for w in combo.windows(2) {
if w[0] >= w[1] {
return Err(CombError::InvalidParameters(
"combination must be strictly ascending".to_string(),
));
}
}
if let Some(&last) = combo.last()
&& last >= n
{
return Err(CombError::OutOfRange {
value: last.to_string(),
bound: n.to_string(),
});
}
Ok(())
}
pub fn combination_rank(combo: &[usize], n: usize) -> Result<BigUint, CombError> {
validate(combo, n)?;
let k = combo.len();
let mut rank = BigUint::from(0u32);
let mut prev = 0usize;
for (i, &c) in combo.iter().enumerate() {
for v in prev..c {
rank += binomial((n - 1 - v) as u64, (k - 1 - i) as u64);
}
prev = c + 1;
}
Ok(rank)
}
pub fn combination_unrank(rank: &BigUint, n: usize, k: usize) -> Result<Vec<usize>, CombError> {
let total = binomial(n as u64, k as u64);
if rank >= &total {
return Err(CombError::OutOfRange {
value: rank.to_string(),
bound: total.to_string(),
});
}
let mut remaining = rank.clone();
let mut combo = Vec::with_capacity(k);
let mut v = 0usize;
for i in 0..k {
loop {
let cnt = binomial((n - 1 - v) as u64, (k - 1 - i) as u64);
if remaining < cnt {
combo.push(v);
v += 1;
break;
}
remaining -= cnt;
v += 1;
}
}
Ok(combo)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lexicographic_order_and_count() {
let all: Vec<_> = combinations(4, 2).unwrap().collect();
assert_eq!(
all,
vec![
vec![0, 1],
vec![0, 2],
vec![0, 3],
vec![1, 2],
vec![1, 3],
vec![2, 3],
]
);
assert_eq!(combinations(5, 3).unwrap().count(), 10);
}
#[test]
fn rank_unrank_round_trip() {
for (i, c) in combinations(6, 3).unwrap().enumerate() {
let r = combination_rank(&c, 6).unwrap();
assert_eq!(r, BigUint::from(i as u32));
assert_eq!(combination_unrank(&r, 6, 3).unwrap(), c);
}
}
#[test]
fn empty_combination_is_singleton() {
assert_eq!(combinations(5, 0).unwrap().count(), 1);
}
}