pub(crate) struct NChooseM {
triangle: Vec<u64>,
nrows: usize,
}
fn row_index(n: usize) -> usize {
(n * n) / 4
}
impl NChooseM {
pub fn new() -> Self {
Self {
triangle: Vec::new(),
nrows: 0,
}
}
pub fn get(&mut self, n: usize, m: usize) -> u64 {
assert!(m <= n, "NChooseM: m ({m}) > n ({n})");
let m = m.min(n - m);
if m == 0 {
return 1;
}
if n >= self.nrows {
self.resize(n + 1);
}
let idx = row_index(n) + m - 1; self.triangle[idx]
}
fn resize(&mut self, new_nrows: usize) {
if new_nrows <= self.nrows {
return;
}
let new_size = row_index(new_nrows);
self.triangle.resize(new_size, 0);
for n in self.nrows..new_nrows {
self.construct_row(n);
}
self.nrows = new_nrows;
}
fn construct_row(&mut self, n: usize) {
let half = n / 2; let base = row_index(n);
if n == 0 {
return; }
if n == 1 {
return; }
for m in 1..=half {
let left = if m == 1 {
1u64
} else {
let prev_m_use = (m - 1).min(n - 1 - (m - 1));
if prev_m_use == 0 {
1u64
} else {
self.triangle[row_index(n - 1) + prev_m_use - 1]
}
};
let right = {
let m_use = m.min(n - 1 - m);
if m_use == 0 {
1u64
} else {
self.triangle[row_index(n - 1) + m_use - 1]
}
};
self.triangle[base + m - 1] = left + right;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_binomial() {
let mut ncm = NChooseM::new();
assert_eq!(ncm.get(0, 0), 1);
assert_eq!(ncm.get(1, 0), 1);
assert_eq!(ncm.get(1, 1), 1);
assert_eq!(ncm.get(5, 2), 10);
assert_eq!(ncm.get(10, 3), 120);
assert_eq!(ncm.get(10, 5), 252);
}
#[test]
fn test_symmetry() {
let mut ncm = NChooseM::new();
for n in 0..20 {
for m in 0..=n {
assert_eq!(ncm.get(n, m), ncm.get(n, n - m));
}
}
}
#[test]
fn test_pascals_rule() {
let mut ncm = NChooseM::new();
for n in 1..15 {
for m in 1..n {
assert_eq!(ncm.get(n, m), ncm.get(n - 1, m - 1) + ncm.get(n - 1, m));
}
}
}
}