use rand::distributions::{Distribution, WeightedIndex};
use rand::RngCore;
const EPSILON: f64 = 1e-9;
#[derive(Debug)]
pub struct Choices<T> {
items: Vec<T>,
weighted_index: WeightedIndex<f64>,
}
impl<T> Choices<T> {
#[inline]
#[allow(clippy::float_arithmetic)]
#[must_use]
pub fn new(items: Vec<T>, weights: &[f64]) -> Self {
assert_eq!(
items.len(),
weights.len(),
"Number of items needs to equal number of weights"
);
assert!(
weights.iter().all(|&w| w >= 0.0f64),
"All weights must be positive numbers since they represent probabilities."
);
let sum_weights: f64 = weights.iter().sum();
assert!(
isclose(sum_weights, 1.0, EPSILON),
"Weights do not sum to 1 \u{b1} \u{3b5}" );
let normalized_weights: Vec<f64> = weights.iter().map(|&w| w / sum_weights).collect();
let weighted_index = WeightedIndex::new(normalized_weights)
.expect("Failed to create WeightedIndex due to invalid weights");
Choices {
items,
weighted_index,
}
}
#[inline]
pub fn sample<R: RngCore>(&self, rng: &mut R) -> &T {
let index = self.weighted_index.sample(rng);
&self.items[index]
}
}
#[inline]
#[allow(clippy::single_call_fn, clippy::float_arithmetic)]
fn isclose(a: f64, b: f64, epsilon: f64) -> bool {
(a - b).abs() <= epsilon
}