Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of Slice distribution #1107

Merged
merged 8 commits into from May 12, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,10 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md).

You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful.

## [0.8.4] - unreleased
### Distributions
- Add slice distribution (#1107)

## [0.8.3] - 2021-01-25
### Fixes
- Fix `no-std` + `alloc` build by gating `choose_multiple_weighted` on `std` (#1088)
Expand Down
18 changes: 11 additions & 7 deletions src/distributions/mod.rs
Expand Up @@ -99,22 +99,28 @@ use core::iter;
pub use self::bernoulli::{Bernoulli, BernoulliError};
pub use self::float::{Open01, OpenClosed01};
pub use self::other::Alphanumeric;
#[doc(inline)] pub use self::uniform::Uniform;
pub use self::slice::Slice;
#[doc(inline)]
pub use self::uniform::Uniform;

#[cfg(feature = "alloc")]
pub use self::weighted_index::{WeightedError, WeightedIndex};

mod bernoulli;
pub mod uniform;

#[deprecated(since = "0.8.0", note = "use rand::distributions::{WeightedIndex, WeightedError} instead")]
#[deprecated(
since = "0.8.0",
note = "use rand::distributions::{WeightedIndex, WeightedError} instead"
)]
#[cfg(feature = "alloc")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
pub mod weighted;
#[cfg(feature = "alloc")] mod weighted_index;
#[cfg(feature = "alloc")]
mod weighted_index;

#[cfg(feature = "serde1")]
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};

mod float;
#[doc(hidden)]
Expand All @@ -123,6 +129,7 @@ pub mod hidden_export {
}
mod integer;
mod other;
mod slice;
mod utils;

/// Types (distributions) that can be used to create a random instance of `T`.
Expand Down Expand Up @@ -200,7 +207,6 @@ impl<'a, T, D: Distribution<T>> Distribution<T> for &'a D {
}
}


/// An iterator that generates random values of `T` with distribution `D`,
/// using `R` as the source of randomness.
///
Expand Down Expand Up @@ -250,7 +256,6 @@ where
{
}


/// A generic random value distribution, implemented for many primitive types.
/// Usually generates values with a numerically uniform distribution, and with a
/// range appropriate to the type.
Expand Down Expand Up @@ -331,7 +336,6 @@ where
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct Standard;


#[cfg(test)]
mod tests {
use super::{Distribution, Uniform};
Expand Down
125 changes: 125 additions & 0 deletions src/distributions/slice.rs
@@ -0,0 +1,125 @@
// Copyright 2021 Developers of the Rand project.
vks marked this conversation as resolved.
Show resolved Hide resolved
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crate::distributions::{Distribution, Uniform};

/// A distribution to sample items uniformly from a slice.
///
/// [`Slice::new`] constructs a distribution referencing a slice and uniformly
/// samples references from the items in the slice. It may do extra work up
/// front to make sampling of multiple values faster; if only one sample from
/// the slice is required, [`SliceRandom::choose`] can be more efficient.
///
/// Steps are taken to avoid bias which might be present in naive
/// implementations; for example `slice[rng.gen() % slice.len()]` samples from
/// the slice, but may be more likely to select numbers in the low range than
/// other values.
///
/// This distribution samples with replacement; each sample is independent.
/// Sampling without replacement requires state to be retained, and therefore
/// cannot be handled by a distribution; you should instead consider methods
/// on [`SliceRandom`], such as [`SliceRandom::choose_multiple`].
///
/// # Example
///
/// ```
/// use rand::Rng;
/// use rand::distributions::Slice;
///
/// let vowels = ['a', 'e', 'i', 'o', 'u'];
/// let vowels_dist = Slice::new(&vowels).unwrap();
/// let rng = rand::thread_rng();
///
/// // build a string of 10 vowels
/// let vowel_string: String = rng
/// .sample_iter(&vowels_dist)
/// .take(10)
/// .collect();
///
/// println!("{}", vowel_string);
/// assert_eq!(vowel_string.len(), 10);
/// assert!(vowel_string.chars().all(|c| vowels.contains(&c)));
/// ```
///
/// For a single sample, [`SliceRandom::choose`][crate::seq::SliceRandom::choose]
/// may be preferred:
///
/// ```
/// use rand::seq::SliceRandom;
///
/// let vowels = ['a', 'e', 'i', 'o', 'u'];
/// let mut rng = rand::thread_rng();
///
/// println!("{}", vowels.choose(&mut rng).unwrap())
/// ```
///
/// [`SliceRandom`]: crate::seq::SliceRandom
/// [`SliceRandom::choose`]: crate::seq::SliceRandom::choose
/// [`SliceRandom::choose_multiple`]: crate::seq::SliceRandom::choose_multiple
#[derive(Debug)]
pub struct Slice<'a, T> {
slice: &'a [T],
range: Uniform<usize>,
}

impl<'a, T> Slice<'a, T> {
/// Create a new `Slice` instance which samples uniformly from the slice.
/// Returns `None` if the slice is empty.
vks marked this conversation as resolved.
Show resolved Hide resolved
pub fn new(slice: &'a [T]) -> Result<Self, EmptySlice> {
match slice.len() {
0 => Err(EmptySlice),
len => Ok(Self {
slice,
range: Uniform::new(0, len),
}),
}
}
}

impl<T> Clone for Slice<'_, T> {
vks marked this conversation as resolved.
Show resolved Hide resolved
fn clone(&self) -> Self {
*self
}
}

impl<T> Copy for Slice<'_, T> {}

impl<'a, T> Distribution<&'a T> for Slice<'a, T> {
fn sample<R: crate::Rng + ?Sized>(&self, rng: &mut R) -> &'a T {
let idx = self.range.sample(rng);

debug_assert!(
idx < self.slice.len(),
"Uniform::new(0, {}) somehow returned {}",
self.slice.len(),
idx
);

// Safety: at construction time, it was ensured that the slice was
// non-empty, and that the `Uniform` range produces values in range
// for the slice
unsafe { self.slice.get_unchecked(idx) }
Lucretiel marked this conversation as resolved.
Show resolved Hide resolved
}
}

/// Error type indicating that a [`Slice`] distribution was improperly
/// constructed with an empty slice.
#[derive(Debug, Clone, Copy)]
pub struct EmptySlice;

impl core::fmt::Display for EmptySlice {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Tried to create a `distributions::Slice` with an empty slice"
)
}
}

#[cfg(feature = "std")]
impl std::error::Error for EmptySlice {}