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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 5 additions & 1 deletion
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

Lines changed: 6 additions & 2 deletions
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

Lines changed: 69 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy