Skip to content

Commit 8929123

Browse files
authored
Add Alphabetic distribution (#1587)
1 parent 06b1642 commit 8929123

File tree

5 files changed

+83
-4
lines changed

5 files changed

+83
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
1010

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

1415
## [0.9.0] - 2025-01-27
1516
### Security and unsafe

benches/benches/standard.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use core::time::Duration;
1010
use criterion::measurement::WallTime;
1111
use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion};
12-
use rand::distr::{Alphanumeric, Open01, OpenClosed01, StandardUniform};
12+
use rand::distr::{Alphabetic, Alphanumeric, Open01, OpenClosed01, StandardUniform};
1313
use rand::prelude::*;
1414
use rand_pcg::Pcg64Mcg;
1515

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

55+
bench_ty::<u8, Alphabetic>(&mut g, "Alphabetic");
5556
bench_ty::<u8, Alphanumeric>(&mut g, "Alphanumeric");
5657

5758
bench_ty::<f32, Open01>(&mut g, "Open01/f32");

src/distr/distribution.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ mod tests {
250250
#[test]
251251
#[cfg(feature = "alloc")]
252252
fn test_dist_string() {
253-
use crate::distr::{Alphanumeric, SampleString, StandardUniform};
253+
use crate::distr::{Alphabetic, Alphanumeric, SampleString, StandardUniform};
254254
use core::str;
255255
let mut rng = crate::test::rng(213);
256256

@@ -261,5 +261,9 @@ mod tests {
261261
let s2 = StandardUniform.sample_string(&mut rng, 20);
262262
assert_eq!(s2.chars().count(), 20);
263263
assert_eq!(str::from_utf8(s2.as_bytes()), Ok(s2.as_str()));
264+
265+
let s3 = Alphabetic.sample_string(&mut rng, 20);
266+
assert_eq!(s3.len(), 20);
267+
assert_eq!(str::from_utf8(s3.as_bytes()), Ok(s3.as_str()));
264268
}
265269
}

src/distr/mod.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
//! numbers of the `char` type; in contrast [`StandardUniform`] may sample any valid
4747
//! `char`.
4848
//!
49+
//! There's also an [`Alphabetic`] distribution which acts similarly to [`Alphanumeric`] but
50+
//! doesn't include digits.
51+
//!
4952
//! For floats (`f32`, `f64`), [`StandardUniform`] samples from `[0, 1)`. Also
5053
//! provided are [`Open01`] (samples from `(0, 1)`) and [`OpenClosed01`]
5154
//! (samples from `(0, 1]`). No option is provided to sample from `[0, 1]`; it
@@ -104,7 +107,7 @@ pub use self::bernoulli::{Bernoulli, BernoulliError};
104107
pub use self::distribution::SampleString;
105108
pub use self::distribution::{Distribution, Iter, Map};
106109
pub use self::float::{Open01, OpenClosed01};
107-
pub use self::other::Alphanumeric;
110+
pub use self::other::{Alphabetic, Alphanumeric};
108111
#[doc(inline)]
109112
pub use self::uniform::Uniform;
110113

@@ -126,7 +129,8 @@ use crate::Rng;
126129
/// code points in the range `0...0x10_FFFF`, except for the range
127130
/// `0xD800...0xDFFF` (the surrogate code points). This includes
128131
/// unassigned/reserved code points.
129-
/// For some uses, the [`Alphanumeric`] distribution will be more appropriate.
132+
/// For some uses, the [`Alphanumeric`] or [`Alphabetic`] distribution will be more
133+
/// appropriate.
130134
/// * `bool` samples `false` or `true`, each with probability 0.5.
131135
/// * Floating point types (`f32` and `f64`) are uniformly distributed in the
132136
/// half-open range `[0, 1)`. See also the [notes below](#floating-point-implementation).

src/distr/other.rs

+69
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ use serde::{Deserialize, Serialize};
7070
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7171
pub struct Alphanumeric;
7272

73+
/// Sample a [`u8`], uniformly distributed over letters:
74+
/// a-z and A-Z.
75+
///
76+
/// # Example
77+
///
78+
/// You're able to generate random Alphabetic characters via mapping or via the
79+
/// [`SampleString::sample_string`] method like so:
80+
///
81+
/// ```
82+
/// use rand::Rng;
83+
/// use rand::distr::{Alphabetic, SampleString};
84+
///
85+
/// // Manual mapping
86+
/// let mut rng = rand::rng();
87+
/// let chars: String = (0..7).map(|_| rng.sample(Alphabetic) as char).collect();
88+
/// println!("Random chars: {}", chars);
89+
///
90+
/// // Using [`SampleString::sample_string`]
91+
/// let string = Alphabetic.sample_string(&mut rand::rng(), 16);
92+
/// println!("Random string: {}", string);
93+
/// ```
94+
///
95+
/// # Passwords
96+
///
97+
/// Refer to [`Alphanumeric#Passwords`].
98+
#[derive(Debug, Clone, Copy, Default)]
99+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100+
pub struct Alphabetic;
101+
73102
// ----- Implementations of distributions -----
74103

75104
impl Distribution<char> for StandardUniform {
@@ -123,6 +152,17 @@ impl Distribution<u8> for Alphanumeric {
123152
}
124153
}
125154

155+
impl Distribution<u8> for Alphabetic {
156+
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
157+
const RANGE: u8 = 26 + 26;
158+
159+
let offset = rng.random_range(0..RANGE) + b'A';
160+
161+
// Account for upper-cases
162+
offset + (offset > b'Z') as u8 * (b'a' - b'Z' - 1)
163+
}
164+
}
165+
126166
#[cfg(feature = "alloc")]
127167
impl SampleString for Alphanumeric {
128168
fn append_string<R: Rng + ?Sized>(&self, rng: &mut R, string: &mut String, len: usize) {
@@ -133,6 +173,20 @@ impl SampleString for Alphanumeric {
133173
}
134174
}
135175

176+
#[cfg(feature = "alloc")]
177+
impl SampleString for Alphabetic {
178+
fn append_string<R: Rng + ?Sized>(&self, rng: &mut R, string: &mut String, len: usize) {
179+
// SAFETY: With this distribution we guarantee that we're working with valid ASCII
180+
// characters.
181+
// See [#1590](https://github.com/rust-random/rand/issues/1590).
182+
unsafe {
183+
let v = string.as_mut_vec();
184+
v.reserve_exact(len);
185+
v.extend(self.sample_iter(rng).take(len));
186+
}
187+
}
188+
}
189+
136190
impl Distribution<bool> for StandardUniform {
137191
#[inline]
138192
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> bool {
@@ -294,6 +348,20 @@ mod tests {
294348
assert!(!incorrect);
295349
}
296350

351+
#[test]
352+
fn test_alphabetic() {
353+
let mut rng = crate::test::rng(806);
354+
355+
// Test by generating a relatively large number of chars, so we also
356+
// take the rejection sampling path.
357+
let mut incorrect = false;
358+
for _ in 0..100 {
359+
let c: char = rng.sample(Alphabetic).into();
360+
incorrect |= !c.is_ascii_alphabetic();
361+
}
362+
assert!(!incorrect);
363+
}
364+
297365
#[test]
298366
fn value_stability() {
299367
fn test_samples<T: Copy + core::fmt::Debug + PartialEq, D: Distribution<T>>(
@@ -321,6 +389,7 @@ mod tests {
321389
],
322390
);
323391
test_samples(&Alphanumeric, 0, &[104, 109, 101, 51, 77]);
392+
test_samples(&Alphabetic, 0, &[97, 102, 89, 116, 75]);
324393
test_samples(&StandardUniform, false, &[true, true, false, true, false]);
325394
test_samples(
326395
&StandardUniform,

0 commit comments

Comments
 (0)