From 585b29f2a9b21170c56fc9a0e29cff664491561c Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Thu, 31 Oct 2024 05:12:55 -0400 Subject: [PATCH] Add top-level helper functions (#1488) Adds random_iter, random_range, random_bool, random_ratio, fill. See also #989, #1503. Co-authored-by: Diggory Hardy --- src/lib.rs | 148 ++++++++++++++++++++++++++++++++++++++++---- src/rng.rs | 4 +- src/seq/iterator.rs | 9 +++ 3 files changed, 147 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 833fe0c0e4..f909292547 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,9 +121,9 @@ pub use rng::{Fill, Rng}; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] use crate::distr::{Distribution, Standard}; -/// Generates a random value using the thread-local random number generator. +/// Generate a random value using the thread-local random number generator. /// -/// This function is simply a shortcut for `rand::rng().gen()`: +/// This function is shorthand for [rng()].[random()](Rng::random): /// /// - See [`ThreadRng`] for documentation of the generator and security /// - See [`Standard`] for documentation of supported types and distributions @@ -142,21 +142,15 @@ use crate::distr::{Distribution, Standard}; /// } /// ``` /// -/// If you're calling `random()` in a loop, caching the generator as in the -/// following example can increase performance. +/// If you're calling `random()` repeatedly, consider using a local `rng` +/// handle to save an initialization-check on each usage: /// /// ``` -/// use rand::Rng; +/// use rand::Rng; // provides the `random` method /// -/// let mut v = vec![1, 2, 3]; -/// -/// for x in v.iter_mut() { -/// *x = rand::random() -/// } -/// -/// // can be made faster by caching rand::rng +/// let mut rng = rand::rng(); // a local handle to the generator /// -/// let mut rng = rand::rng(); +/// let mut v = vec![1, 2, 3]; /// /// for x in v.iter_mut() { /// *x = rng.random(); @@ -174,6 +168,127 @@ where rng().random() } +/// Return an iterator over [`random()`] variates +/// +/// This function is shorthand for +/// [rng()].[random_iter](Rng::random_iter)(). +/// +/// # Example +/// +/// ``` +/// let v: Vec = rand::random_iter().take(5).collect(); +/// println!("{v:?}"); +/// ``` +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +pub fn random_iter() -> distr::DistIter +where + Standard: Distribution, +{ + rng().random_iter() +} + +/// Generate a random value in the given range using the thread-local random number generator. +/// +/// This function is shorthand for +/// [rng()].[random_range](Rng::random_range)(range). +/// +/// # Example +/// +/// ``` +/// let y: f32 = rand::random_range(0.0..=1e9); +/// println!("{}", y); +/// +/// let words: Vec<&str> = "Mary had a little lamb".split(' ').collect(); +/// println!("{}", words[rand::random_range(..words.len())]); +/// ``` +/// Note that the first example can also be achieved (without `collect`'ing +/// to a `Vec`) using [`seq::IteratorRandom::choose`]. +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +pub fn random_range(range: R) -> T +where + T: distr::uniform::SampleUniform, + R: distr::uniform::SampleRange, +{ + rng().random_range(range) +} + +/// Return a bool with a probability `p` of being true. +/// +/// This function is shorthand for +/// [rng()].[random_bool](Rng::random_bool)(p). +/// +/// # Example +/// +/// ``` +/// println!("{}", rand::random_bool(1.0 / 3.0)); +/// ``` +/// +/// # Panics +/// +/// If `p < 0` or `p > 1`. +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn random_bool(p: f64) -> bool { + rng().random_bool(p) +} + +/// Return a bool with a probability of `numerator/denominator` of being +/// true. +/// +/// That is, `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of +/// returning true. If `numerator == denominator`, then the returned value +/// is guaranteed to be `true`. If `numerator == 0`, then the returned +/// value is guaranteed to be `false`. +/// +/// See also the [`Bernoulli`] distribution, which may be faster if +/// sampling from the same `numerator` and `denominator` repeatedly. +/// +/// This function is shorthand for +/// [rng()].[random_ratio](Rng::random_ratio)(numerator, denominator). +/// +/// # Panics +/// +/// If `denominator == 0` or `numerator > denominator`. +/// +/// # Example +/// +/// ``` +/// println!("{}", rand::random_ratio(2, 3)); +/// ``` +/// +/// [`Bernoulli`]: distr::Bernoulli +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn random_ratio(numerator: u32, denominator: u32) -> bool { + rng().random_ratio(numerator, denominator) +} + +/// Fill any type implementing [`Fill`] with random data +/// +/// This function is shorthand for +/// [rng()].[fill](Rng::fill)(dest). +/// +/// # Example +/// +/// ``` +/// let mut arr = [0i8; 20]; +/// rand::fill(&mut arr[..]); +/// ``` +/// +/// Note that you can instead use [`random()`] to generate an array of random +/// data, though this is slower for small elements (smaller than the RNG word +/// size). +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn fill(dest: &mut T) { + dest.fill(&mut rng()) +} + #[cfg(test)] mod test { use super::*; @@ -200,4 +315,11 @@ mod test { (f32, (f64, (f64,))), ) = random(); } + + #[test] + #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] + fn test_range() { + let _n: usize = random_range(42..=43); + let _f: f32 = random_range(42.0..43.0); + } } diff --git a/src/rng.rs b/src/rng.rs index a3657ed45f..9ac481ed9c 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -196,7 +196,9 @@ pub trait Rng: RngCore { } /// Return a bool with a probability of `numerator/denominator` of being - /// true. I.e. `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of + /// true. + /// + /// That is, `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of /// returning true. If `numerator == denominator`, then the returned value /// is guaranteed to be `true`. If `numerator == 0`, then the returned /// value is guaranteed to be `false`. diff --git a/src/seq/iterator.rs b/src/seq/iterator.rs index ad96b3baf7..b10d205676 100644 --- a/src/seq/iterator.rs +++ b/src/seq/iterator.rs @@ -54,6 +54,15 @@ pub trait IteratorRandom: Iterator + Sized { /// Consider instead using [`IteratorRandom::choose_stable`] to avoid /// [`Iterator`] combinators which only change size hints from affecting the /// results. + /// + /// # Example + /// + /// ``` + /// use rand::seq::IteratorRandom; + /// + /// let words = "Mary had a little lamb".split(' '); + /// println!("{}", words.choose(&mut rand::rng()).unwrap()); + /// ``` fn choose(mut self, rng: &mut R) -> Option where R: Rng + ?Sized,