use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
#[derive(Debug)]
pub struct ColumnSampler {
pool: Vec<u32>,
bylevel: f64,
bynode: f64,
rng: StdRng,
}
impl ColumnSampler {
pub fn new(pool: Vec<u32>, bylevel: f64, bynode: f64, seed: u64) -> Self {
ColumnSampler {
pool,
bylevel,
bynode,
rng: StdRng::seed_from_u64(seed),
}
}
pub fn all(n_features: usize) -> Self {
ColumnSampler::new((0..n_features as u32).collect(), 1.0, 1.0, 0)
}
pub fn sample(&mut self) -> Vec<u32> {
let level = subsample(&self.pool, self.bylevel, &mut self.rng);
subsample(&level, self.bynode, &mut self.rng)
}
}
fn subsample(pool: &[u32], ratio: f64, rng: &mut StdRng) -> Vec<u32> {
if ratio >= 1.0 || pool.len() <= 1 {
return pool.to_vec();
}
let k = ((ratio * pool.len() as f64).round() as usize).clamp(1, pool.len());
let mut idx = pool.to_vec();
idx.shuffle(rng);
idx.truncate(k);
idx.sort_unstable();
idx
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pass_through_when_ratios_one() {
let mut s = ColumnSampler::all(10);
let f = s.sample();
assert_eq!(f, (0..10u32).collect::<Vec<_>>());
}
#[test]
fn composes_bylevel_and_bynode() {
let pool: Vec<u32> = (0..100).collect();
let mut s = ColumnSampler::new(pool.clone(), 0.5, 0.5, 42);
let f = s.sample();
assert_eq!(f.len(), 25); assert!(f.windows(2).all(|w| w[0] < w[1]), "sorted & unique");
assert!(f.iter().all(|x| pool.contains(x)));
}
#[test]
fn deterministic_for_seed() {
let pool: Vec<u32> = (0..50).collect();
let mut a = ColumnSampler::new(pool.clone(), 0.6, 1.0, 7);
let mut b = ColumnSampler::new(pool, 0.6, 1.0, 7);
assert_eq!(a.sample(), b.sample());
}
#[test]
fn always_at_least_one() {
let pool: Vec<u32> = (0..3).collect();
let mut s = ColumnSampler::new(pool, 0.01, 0.01, 1);
assert!(!s.sample().is_empty());
}
}