use rand::Rng;
#[must_use]
pub fn pick_from<T: Copy>(pool: &[T], fallback: T) -> T {
let mut rng = rand::thread_rng();
pick_from_rng(pool, fallback, &mut rng)
}
#[must_use]
pub fn pick_from_rng<T: Copy, R: Rng + ?Sized>(pool: &[T], fallback: T, rng: &mut R) -> T {
match pick_ref_from_rng(pool, rng) {
Some(value) => *value,
None => fallback,
}
}
#[must_use]
pub fn pick_ref_from_rng<'a, T, R: Rng + ?Sized>(pool: &'a [T], rng: &mut R) -> Option<&'a T> {
if pool.is_empty() {
return None;
}
Some(&pool[rng.gen_range(0..pool.len())])
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{SeedableRng, rngs::StdRng};
use std::collections::HashSet;
#[test]
fn empty_pool_returns_fallback() {
let empty: &[&str] = &[];
assert_eq!(pick_from(empty, "default"), "default");
let empty_u8: &[u8] = &[];
assert_eq!(pick_from(empty_u8, 0x42), 0x42);
}
#[test]
fn single_entry_pool_always_returns_that_entry() {
let single = &["only"];
for _ in 0..50 {
assert_eq!(pick_from(single, "fallback"), "only");
}
}
#[test]
fn many_calls_eventually_hit_every_pool_entry() {
let pool = &["a", "b", "c", "d", "e"];
let mut seen: HashSet<&str> = HashSet::new();
for _ in 0..1000 {
seen.insert(pick_from(pool, "fallback"));
}
assert_eq!(seen.len(), pool.len(), "must hit every pool entry");
}
#[test]
fn pick_returns_pool_entry_not_fallback_when_non_empty() {
let pool = &[42_u64, 100, 1000];
for _ in 0..50 {
let v = pick_from(pool, 0xDEAD_BEEF);
assert!(
pool.contains(&v),
"pick returned non-pool value {v} on non-empty pool"
);
}
}
#[test]
fn works_with_byte_pool() {
let bytes: &[u8] = b"ABC";
for _ in 0..30 {
let v = pick_from(bytes, 0);
assert!(bytes.contains(&v));
}
}
#[test]
fn pick_from_rng_uses_caller_rng_and_fallback_contract() {
let mut rng = StdRng::seed_from_u64(11);
let pool = &["alpha", "beta", "gamma"];
for _ in 0..30 {
let value = pick_from_rng(pool, "fallback", &mut rng);
assert!(pool.contains(&value));
}
let empty: &[&str] = &[];
assert_eq!(pick_from_rng(empty, "fallback", &mut rng), "fallback");
}
#[test]
fn pick_ref_from_rng_returns_borrowed_entries_or_none() {
let mut rng = StdRng::seed_from_u64(17);
let pool = vec!["a".to_string(), "b".to_string(), "c".to_string()];
for _ in 0..30 {
let value = pick_ref_from_rng(&pool, &mut rng).expect("non-empty pool should sample");
assert!(pool.contains(value));
}
let empty: Vec<String> = Vec::new();
assert!(pick_ref_from_rng(&empty, &mut rng).is_none());
}
}