rand_select/lib.rs
1//!
2//! The RandomSelector selects among weighted choices, without bias.
3//!
4//! ```
5//! use rand_select::RandomSelector;
6//! let selector = RandomSelector::default()
7//! .with(1.0, 'A')
8//! .with(1.5, 'B')
9//! .with_none(3.0);
10//! let l = selector.select();
11//! // l has half a chance to be None, and is 50% more likely to be 'B' than 'A'
12//! ```
13//!
14//! If you set a value and call neither `with_none` nor `with_none_up_to`, the selector will always return a value.
15//!
16//! If you have already normalized weight, `with_none_up_to` is a convenient way to set the total weight of the selector:
17//!
18//! ```
19//! use rand_select::RandomSelector;
20//! let selector = RandomSelector::default()
21//! .with(0.1, 'A')
22//! .with(0.2, 'B')
23//! .with_none_up_to(1.0);
24//! ```
25//! The RandomSelector is designed for reuse, and can use the RNG of your choice.
26
27use {
28 rand::Rng,
29};
30
31#[derive(Clone)]
32struct Choice<T> {
33 weight: f64,
34 value: T,
35}
36
37/// A selector allowing to randomly select a value from a set of choices, each with an associated weight.
38///
39/// ```
40/// use rand_select::RandomSelector;
41/// let selector = RandomSelector::default()
42/// .with(1.0, 'A')
43/// .with(1.5, 'B')
44/// .with_none(3.0);
45/// let l = selector.select();
46/// // l has half a chance to be None, and is 50% more likely to be 'B' than 'A'
47/// ```
48#[derive(Clone, Default)]
49pub struct RandomSelector<T> {
50 choices: Vec<Choice<T>>,
51 total_weight: f64,
52}
53
54impl<T> RandomSelector<T> {
55 pub fn with(mut self, weight: f64, value: T) -> Self {
56 self.choices.push(Choice { weight, value });
57 self.total_weight += weight.abs();
58 self
59 }
60 pub fn with_none(mut self, weight: f64) -> Self {
61 self.total_weight += weight.abs();
62 self
63 }
64 /// Complete choices to be None up to the given weight.
65 ///
66 /// This is convenient where all choices set are conventionnaly already normalized:
67 ///
68 /// ```
69 /// use rand_select::RandomSelector;
70 /// let selector = RandomSelector::default()
71 /// .with(0.1, 'A')
72 /// .with(0.2, 'B')
73 /// .with_none_up_to(1.0);
74 /// ```
75 pub fn with_none_up_to(mut self, total_weight: f64) -> Self {
76 self.total_weight = total_weight.abs();
77 self
78 }
79 /// Select a random value among the provided ones.
80 pub fn select(&self) -> Option<&T> {
81 let mut rng = rand::rng();
82 self.select_with_rng(&mut rng)
83 }
84 /// Select a random value among the provided ones, with the generator of your choice.
85 pub fn select_with_rng<R: Rng>(&self, mut r: R) -> Option<&T> {
86 if self.total_weight == 0.0 {
87 return None;
88 }
89 let random_value: f64 = r.random_range(0.0..self.total_weight);
90 let mut cumulative_weight = 0.0;
91 for choice in &self.choices {
92 cumulative_weight += choice.weight;
93 if random_value < cumulative_weight {
94 return Some(&choice.value);
95 }
96 }
97 None
98 }
99}