Skip to content

Commit

Permalink
Add Alphabetic distribution (#1587)
Browse files Browse the repository at this point in the history
  • Loading branch information
1Git2Clone authored Feb 15, 2025
1 parent 06b1642 commit 8929123
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.

## [Unreleased]
- Fix feature `simd_support` for recent nightly rust (#1586)
- Add `Alphabetic` distribution. (#1587)

## [0.9.0] - 2025-01-27
### Security and unsafe
Expand Down
3 changes: 2 additions & 1 deletion benches/benches/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use core::time::Duration;
use criterion::measurement::WallTime;
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
use rand::distr::{Alphanumeric, Open01, OpenClosed01, StandardUniform};
use rand::distr::{Alphabetic, Alphanumeric, Open01, OpenClosed01, StandardUniform};
use rand::prelude::*;
use rand_pcg::Pcg64Mcg;

Expand Down Expand Up @@ -52,6 +52,7 @@ pub fn bench(c: &mut Criterion) {
do_ty!(f32, f64);
do_ty!(char);

bench_ty::<u8, Alphabetic>(&mut g, "Alphabetic");
bench_ty::<u8, Alphanumeric>(&mut g, "Alphanumeric");

bench_ty::<f32, Open01>(&mut g, "Open01/f32");
Expand Down
6 changes: 5 additions & 1 deletion src/distr/distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ mod tests {
#[test]
#[cfg(feature = "alloc")]
fn test_dist_string() {
use crate::distr::{Alphanumeric, SampleString, StandardUniform};
use crate::distr::{Alphabetic, Alphanumeric, SampleString, StandardUniform};
use core::str;
let mut rng = crate::test::rng(213);

Expand All @@ -261,5 +261,9 @@ mod tests {
let s2 = StandardUniform.sample_string(&mut rng, 20);
assert_eq!(s2.chars().count(), 20);
assert_eq!(str::from_utf8(s2.as_bytes()), Ok(s2.as_str()));

let s3 = Alphabetic.sample_string(&mut rng, 20);
assert_eq!(s3.len(), 20);
assert_eq!(str::from_utf8(s3.as_bytes()), Ok(s3.as_str()));
}
}
8 changes: 6 additions & 2 deletions src/distr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
//! numbers of the `char` type; in contrast [`StandardUniform`] may sample any valid
//! `char`.
//!
//! There's also an [`Alphabetic`] distribution which acts similarly to [`Alphanumeric`] but
//! doesn't include digits.
//!
//! For floats (`f32`, `f64`), [`StandardUniform`] samples from `[0, 1)`. Also
//! provided are [`Open01`] (samples from `(0, 1)`) and [`OpenClosed01`]
//! (samples from `(0, 1]`). No option is provided to sample from `[0, 1]`; it
Expand Down Expand Up @@ -104,7 +107,7 @@ pub use self::bernoulli::{Bernoulli, BernoulliError};
pub use self::distribution::SampleString;
pub use self::distribution::{Distribution, Iter, Map};
pub use self::float::{Open01, OpenClosed01};
pub use self::other::Alphanumeric;
pub use self::other::{Alphabetic, Alphanumeric};
#[doc(inline)]
pub use self::uniform::Uniform;

Expand All @@ -126,7 +129,8 @@ use crate::Rng;
/// code points in the range `0...0x10_FFFF`, except for the range
/// `0xD800...0xDFFF` (the surrogate code points). This includes
/// unassigned/reserved code points.
/// For some uses, the [`Alphanumeric`] distribution will be more appropriate.
/// For some uses, the [`Alphanumeric`] or [`Alphabetic`] distribution will be more
/// appropriate.
/// * `bool` samples `false` or `true`, each with probability 0.5.
/// * Floating point types (`f32` and `f64`) are uniformly distributed in the
/// half-open range `[0, 1)`. See also the [notes below](#floating-point-implementation).
Expand Down
69 changes: 69 additions & 0 deletions src/distr/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,35 @@ use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Alphanumeric;

/// Sample a [`u8`], uniformly distributed over letters:
/// a-z and A-Z.
///
/// # Example
///
/// You're able to generate random Alphabetic characters via mapping or via the
/// [`SampleString::sample_string`] method like so:
///
/// ```
/// use rand::Rng;
/// use rand::distr::{Alphabetic, SampleString};
///
/// // Manual mapping
/// let mut rng = rand::rng();
/// let chars: String = (0..7).map(|_| rng.sample(Alphabetic) as char).collect();
/// println!("Random chars: {}", chars);
///
/// // Using [`SampleString::sample_string`]
/// let string = Alphabetic.sample_string(&mut rand::rng(), 16);
/// println!("Random string: {}", string);
/// ```
///
/// # Passwords
///
/// Refer to [`Alphanumeric#Passwords`].
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Alphabetic;

// ----- Implementations of distributions -----

impl Distribution<char> for StandardUniform {
Expand Down Expand Up @@ -123,6 +152,17 @@ impl Distribution<u8> for Alphanumeric {
}
}

impl Distribution<u8> for Alphabetic {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
const RANGE: u8 = 26 + 26;

let offset = rng.random_range(0..RANGE) + b'A';

// Account for upper-cases
offset + (offset > b'Z') as u8 * (b'a' - b'Z' - 1)
}
}

#[cfg(feature = "alloc")]
impl SampleString for Alphanumeric {
fn append_string<R: Rng + ?Sized>(&self, rng: &mut R, string: &mut String, len: usize) {
Expand All @@ -133,6 +173,20 @@ impl SampleString for Alphanumeric {
}
}

#[cfg(feature = "alloc")]
impl SampleString for Alphabetic {
fn append_string<R: Rng + ?Sized>(&self, rng: &mut R, string: &mut String, len: usize) {
// SAFETY: With this distribution we guarantee that we're working with valid ASCII
// characters.
// See [#1590](https://github.com/rust-random/rand/issues/1590).
unsafe {
let v = string.as_mut_vec();
v.reserve_exact(len);
v.extend(self.sample_iter(rng).take(len));
}
}
}

impl Distribution<bool> for StandardUniform {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> bool {
Expand Down Expand Up @@ -294,6 +348,20 @@ mod tests {
assert!(!incorrect);
}

#[test]
fn test_alphabetic() {
let mut rng = crate::test::rng(806);

// Test by generating a relatively large number of chars, so we also
// take the rejection sampling path.
let mut incorrect = false;
for _ in 0..100 {
let c: char = rng.sample(Alphabetic).into();
incorrect |= !c.is_ascii_alphabetic();
}
assert!(!incorrect);
}

#[test]
fn value_stability() {
fn test_samples<T: Copy + core::fmt::Debug + PartialEq, D: Distribution<T>>(
Expand Down Expand Up @@ -321,6 +389,7 @@ mod tests {
],
);
test_samples(&Alphanumeric, 0, &[104, 109, 101, 51, 77]);
test_samples(&Alphabetic, 0, &[97, 102, 89, 116, 75]);
test_samples(&StandardUniform, false, &[true, true, false, true, false]);
test_samples(
&StandardUniform,
Expand Down

0 comments on commit 8929123

Please sign in to comment.