Skip to main content

malachite_base/chars/
random.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::bools::random::{WeightedRandomBools, weighted_random_bools};
10use crate::chars::char_is_graphic;
11use crate::chars::crement::{char_to_contiguous_range, contiguous_range_to_char, decrement_char};
12use crate::num::random::{RandomUnsignedInclusiveRange, random_unsigned_inclusive_range};
13use crate::random::Seed;
14use crate::vecs::{RandomValuesFromVec, random_values_from_vec};
15
16/// Uniformly generates random [`char`]s in a closed interval.
17///
18/// This `struct` is created by [`random_char_range`] and [`random_char_inclusive_range`]; see their
19/// documentation for more.
20#[derive(Clone, Debug)]
21pub struct RandomCharRange {
22    chunks: RandomUnsignedInclusiveRange<u32>,
23}
24
25impl Iterator for RandomCharRange {
26    type Item = char;
27
28    #[inline]
29    fn next(&mut self) -> Option<char> {
30        contiguous_range_to_char(self.chunks.next().unwrap())
31    }
32}
33
34/// Uniformly generates random [`char`]s in a closed interval, weighting graphic and non-graphic
35/// [`char`]s separately.
36///
37/// This `struct` is created by [`graphic_weighted_random_char_range`] and
38/// [`graphic_weighted_random_char_inclusive_range`]; see their documentation for more.
39#[derive(Clone, Debug)]
40pub struct WeightedGraphicRandomCharRange {
41    xs: WeightedRandomBools,
42    graphic: RandomValuesFromVec<char>,
43    non_graphic: RandomValuesFromVec<char>,
44}
45
46impl Iterator for WeightedGraphicRandomCharRange {
47    type Item = char;
48
49    fn next(&mut self) -> Option<char> {
50        if self.xs.next().unwrap() {
51            self.graphic.next()
52        } else {
53            self.non_graphic.next()
54        }
55    }
56}
57
58/// Uniformly generates random [`char`]s.
59///
60/// $P(c) = \frac{1}{2^{20}+2^{16}-2^{11}}$.
61///
62/// The output length is infinite.
63///
64/// # Worst-case complexity per iteration
65/// Constant time and additional memory.
66///
67/// # Examples
68/// ```
69/// use malachite_base::chars::random::random_chars;
70/// use malachite_base::random::EXAMPLE_SEED;
71///
72/// assert_eq!(
73///     random_chars(EXAMPLE_SEED)
74///         .take(10)
75///         .collect::<String>()
76///         .as_str(),
77///     "\u{5f771}\u{87234}\u{bcd36}\u{9e195}\u{5da07}\u{36553}\u{45028}\u{1cdfd}\u{d8530}\u{c7f2e}"
78/// )
79/// ```
80#[inline]
81pub fn random_chars(seed: Seed) -> RandomCharRange {
82    random_char_inclusive_range(seed, char::MIN, char::MAX)
83}
84
85/// Uniformly generates random ASCII [`char`]s.
86///
87/// $$
88/// P(c) = \\begin{cases}
89///     2^{-7} & \text{if} \\quad c < \\backslash\\text{u\\{0x80\\}} \\\\
90///     0 & \\text{otherwise}
91/// \\end{cases}
92/// $$
93///
94/// The output length is infinite.
95///
96/// # Worst-case complexity per iteration
97/// Constant time and additional memory.
98///
99/// # Examples
100/// ```
101/// use malachite_base::chars::random::random_ascii_chars;
102/// use malachite_base::random::EXAMPLE_SEED;
103///
104/// assert_eq!(
105///     random_ascii_chars(EXAMPLE_SEED)
106///         .take(20)
107///         .collect::<String>()
108///         .as_str(),
109///     "q^\u{17}bF\\4T!/\u{1}q6\n/\u{11}Y\\wB"
110/// )
111/// ```
112#[inline]
113pub fn random_ascii_chars(seed: Seed) -> RandomCharRange {
114    random_char_inclusive_range(seed, char::MIN, '\u{7f}')
115}
116
117/// Uniformly generates random [`char`]s in the half-open interval $[a, b)$.
118///
119/// $a$ must be less than $b$. This function cannot create a range that includes `char::MAX`; for
120/// that, use [`random_char_inclusive_range`].
121///
122/// $$
123/// P(x) = \\begin{cases}
124///     \frac{1}
125///     {\mathrm{char\\_to\\_contiguous\\_range(b)}-\mathrm{char\\_to\\_contiguous\\_range(a)}} &
126///         \text{if} \\quad a \leq x < b \\\\
127///     0 & \\text{otherwise}
128/// \\end{cases}
129/// $$
130///
131/// The output length is infinite.
132///
133/// # Expected complexity per iteration
134/// Constant time and additional memory.
135///
136/// # Panics
137/// Panics if $a \geq b$.
138///
139/// # Examples
140/// ```
141/// use malachite_base::chars::random::random_char_range;
142/// use malachite_base::random::EXAMPLE_SEED;
143///
144/// assert_eq!(
145///     random_char_range(EXAMPLE_SEED, 'a', 'z')
146///         .take(50)
147///         .collect::<String>()
148///         .as_str(),
149///     "rlewrsgkdlbeouylrelopxqkoonftexoshqulgvonioatekqes"
150/// )
151/// ```
152#[inline]
153pub fn random_char_range(seed: Seed, a: char, mut b: char) -> RandomCharRange {
154    assert!(a < b, "a must be less than b. a: {a}, b: {b}");
155    decrement_char(&mut b);
156    random_char_inclusive_range(seed, a, b)
157}
158
159/// Uniformly generates random [`char`]s in the closed interval $[a, b]$.
160///
161/// $a$ must be less than or equal to $b$.
162///
163/// $$
164/// P(x) = \\begin{cases}
165///     \frac{1}
166///         {\mathrm{char\\_to\\_contiguous\\_range(b)}-\mathrm{char\\_to\\_contiguous\\_range(a)}
167///         +1} &
168///         \text{if} \\quad a \leq x < b \\\\
169///     0 & \\text{otherwise}
170/// \\end{cases}
171/// $$
172///
173/// The output length is infinite.
174///
175/// # Expected complexity per iteration
176/// Constant time and additional memory.
177///
178/// # Panics
179/// Panics if $a > b$.
180///
181/// # Examples
182/// ```
183/// use malachite_base::chars::random::random_char_inclusive_range;
184/// use malachite_base::random::EXAMPLE_SEED;
185///
186/// assert_eq!(
187///     random_char_inclusive_range(EXAMPLE_SEED, 'a', 'z')
188///         .take(50)
189///         .collect::<String>()
190///         .as_str(),
191///     "rlewrsgkdlbeouylrelopxqkoonftexoshqulgvonioatekqes"
192/// )
193/// ```
194#[inline]
195pub fn random_char_inclusive_range(seed: Seed, a: char, b: char) -> RandomCharRange {
196    assert!(a <= b, "a must be less than or equal to b. a: {a}, b: {b}");
197    RandomCharRange {
198        chunks: random_unsigned_inclusive_range(
199            seed,
200            char_to_contiguous_range(a),
201            char_to_contiguous_range(b),
202        ),
203    }
204}
205
206/// Generates random [`char`]s, weighting graphic and non-graphic [`char`]s separately.
207///
208/// See [`char_is_graphic`] for the definition of a graphic [`char`].
209///
210/// Let $n_p$ be `p_numerator` and $d_p$ be `p_denominator`, and let $p = p_n/p_d$.
211///
212/// The set of graphic [`char`]s is selected with probability $p$, and the set of non-graphic
213/// [`char`]s with probability $1-p$. Then, a [`char`] is selected uniformly from the appropriate
214/// set. There are 142,523 graphic [`char`]s out of 1,112,064, so we have
215///
216/// $$
217/// P(x) = \\begin{cases}
218///     \frac{p}{142523} & \text{if} \\quad x \\ \\text{is} \\ \\text{graphic} \\\\
219///     \frac{1-p}{969541} & \\text{otherwise}
220/// \\end{cases}
221/// $$
222///
223/// To recover the uniform distribution, use $p = 142523/1112064$, which is roughly $1/8$.
224///
225/// The output length is infinite.
226///
227/// # Expected complexity per iteration
228/// Constant time and additional memory.
229///
230/// # Panics
231/// Panics if `p_denominator` is zero or `p_denominator > p_denominator`.
232///
233/// # Examples
234/// ```
235/// use malachite_base::chars::random::graphic_weighted_random_chars;
236/// use malachite_base::random::EXAMPLE_SEED;
237///
238/// assert_eq!(
239///     graphic_weighted_random_chars(EXAMPLE_SEED, 10, 11)
240///         .take(20)
241///         .collect::<String>()
242///         .as_str(),
243///     "𗄥𭼱礟깯ꅌ板쭚𫆰╵𲐙𡻁⢑𲣑\u{9013d}𮛎瀍𰥺\u{3a6f1}\u{d9adc}𲛆"
244/// )
245/// ```
246#[inline]
247pub fn graphic_weighted_random_chars(
248    seed: Seed,
249    p_numerator: u64,
250    p_denominator: u64,
251) -> WeightedGraphicRandomCharRange {
252    graphic_weighted_random_char_inclusive_range(
253        seed,
254        char::MIN,
255        char::MAX,
256        p_numerator,
257        p_denominator,
258    )
259}
260
261/// Generates random ASCII [`char`]s, weighting graphic and non-graphic [`char`]s separately.
262///
263/// See [`char_is_graphic`] for the definition of a graphic [`char`].
264///
265/// Let $n_p$ be `p_numerator` and $d_p$ be `p_denominator`, and let $p = p_n/p_d$.
266///
267/// The set of graphic ASCII [`char`]s is selected with probability $p$, and the set of non-graphic
268/// ASCII [`char`]s with probability $1-p$. Then, a [`char`] is selected uniformly from the
269/// appropriate set. There are 95 graphic ASCII [`char`]s out of 128, so we have
270///
271/// $$
272/// P(x) = \\begin{cases}
273///     \frac{p}{95} & \text{if} \\quad
274///     x < \\backslash\\text{u\\{0x80\\}} \\ \\text{and} \\ x \\ \\text{is graphic} \\\\
275///     \frac{1-p}{33} & \text{if} \\quad
276///     x < \\backslash\\text{u\\{0x80\\}} \\ \\text{and} \\ x \\ \\text{is not graphic} \\\\
277///     0 & \\text{otherwise}
278/// \\end{cases}
279/// $$
280///
281/// To recover the uniform distribution, use $p = 95/128$.
282///
283/// The output length is infinite.
284///
285/// # Expected complexity per iteration
286/// Constant time and additional memory.
287///
288/// # Panics
289/// Panics if `p_denominator` is zero or `p_denominator > p_denominator`.
290///
291/// # Examples
292/// ```
293/// use malachite_base::chars::random::graphic_weighted_random_ascii_chars;
294/// use malachite_base::random::EXAMPLE_SEED;
295///
296/// assert_eq!(
297///     graphic_weighted_random_ascii_chars(EXAMPLE_SEED, 10, 11)
298///         .take(40)
299///         .collect::<String>()
300///         .as_str(),
301///     "x14N(bcXr$g)7\u{1b}/E+\u{8}\rf\u{2}\u{11}Y\u{11}Poo.$V2R.$V=6\u{13}\t\u{11}"
302/// )
303/// ```
304#[inline]
305pub fn graphic_weighted_random_ascii_chars(
306    seed: Seed,
307    p_numerator: u64,
308    p_denominator: u64,
309) -> WeightedGraphicRandomCharRange {
310    graphic_weighted_random_char_inclusive_range(
311        seed,
312        char::MIN,
313        '\u{7f}',
314        p_numerator,
315        p_denominator,
316    )
317}
318
319/// Generates random [`char`]s in the half-open interval $[a, b)$, weighting graphic and non-graphic
320/// [`char`]s separately.
321///
322/// See [`char_is_graphic`] for the definition of a graphic [`char`].
323///
324/// Let $n_p$ be `p_numerator` and $d_p$ be `p_denominator`, and let $p = p_n/p_d$.
325///
326/// The set of graphic [`char`]s in the specified range is selected with probability $p$, and the
327/// set of non-graphic [`char`]s in the range with probability $1-p$. Then, a [`char`] is selected
328/// uniformly from the appropriate set.
329///
330/// $a$ must be less than $b$. Furthermore, $[a, b)$ must contain both graphic and non-graphic
331/// [`char`]s. This function cannot create a range that includes `char::MAX`; for that, use
332/// [`graphic_weighted_random_char_inclusive_range`].
333///
334/// Let $g$ be the number of graphic [`char`]s in $[a, b)$. Then we have
335///
336/// $$
337/// P(x) = \\begin{cases}
338///     \frac{p}{g} & a \leq x < b \\ \\text{and} \\ x \\ \\text{is graphic} \\\\
339///     \frac{1-p}{b-a-g} & a \leq x < b \\ \\text{and} \\ x \\ \\text{is not graphic} \\\\
340///     0 & \\text{otherwise}
341/// \\end{cases}
342/// $$
343///
344/// To recover the uniform distribution, use $p = g/(b-a)$.
345///
346/// The output length is infinite.
347///
348/// # Expected complexity per iteration
349/// Constant time and additional memory.
350///
351/// # Panics
352/// Panics if `p_denominator` is zero or `p_denominator > p_denominator`, if $a \geq b$, if $[a, b)$
353/// contains no graphic [`char`]s, or if $[a, b)$ contains only graphic [`char`]s.
354///
355/// # Examples
356/// ```
357/// use malachite_base::chars::random::graphic_weighted_random_char_range;
358/// use malachite_base::random::EXAMPLE_SEED;
359///
360/// assert_eq!(
361///     graphic_weighted_random_char_range(EXAMPLE_SEED, '\u{100}', '\u{400}', 10, 11)
362///         .take(30)
363///         .collect::<String>()
364///         .as_str(),
365///     "ǘɂŜȢΙƘƣʅΰǟ˳ˊȇ\u{31b}ʰɥΈ\u{324}\u{35a}Ϟ\u{367}\u{337}ƃ\u{342}ʌμƢϳϪǰ"
366/// )
367/// ```
368#[inline]
369pub fn graphic_weighted_random_char_range(
370    seed: Seed,
371    a: char,
372    mut b: char,
373    p_numerator: u64,
374    p_denominator: u64,
375) -> WeightedGraphicRandomCharRange {
376    assert!(a < b, "a must be less than b. a: {a}, b: {b}");
377    decrement_char(&mut b);
378    graphic_weighted_random_char_inclusive_range(seed, a, b, p_numerator, p_denominator)
379}
380
381/// Generates random [`char`]s in the closed interval $[a, b]$, weighting graphic and non-graphic
382/// [`char`]s separately.
383///
384/// See [`char_is_graphic`] for the definition of a graphic [`char`].
385///
386/// Let $n_p$ be `p_numerator` and $d_p$ be `p_denominator`, and let $p = p_n/p_d$.
387///
388/// The set of graphic [`char`]s in the specified range is selected with probability $p$, and the
389/// set of non-graphic [`char`]s in the range with probability $1-p$. Then, a [`char`] is selected
390/// uniformly from the appropriate set.
391///
392/// $a$ must be less than $b$. Furthermore, $[a, b]$ must contain both graphic and non-graphic
393/// [`char`]s. This function cannot create a range that includes `char::MAX`; for that, use
394/// [`graphic_weighted_random_char_inclusive_range`].
395///
396/// Let $g$ be the number of graphic [`char`]s in $[a, b]$. Then we have
397///
398/// $$
399/// P(x) = \\begin{cases}
400///     \frac{p}{g} & a \leq x < b \\ \\text{and} \\ x \\ \\text{is graphic} \\\\
401///     \frac{1-p}{b-a-g+1} & a \leq x < b \\ \\text{and} \\ x \\ \\text{is not graphic} \\\\
402///     0 & \\text{otherwise}
403/// \\end{cases}
404/// $$
405///
406/// To recover the uniform distribution, use $p = g/(b-a+1)$.
407///
408/// The output length is infinite.
409///
410/// # Expected complexity per iteration
411/// Constant time and additional memory.
412///
413/// # Panics
414/// Panics if `p_denominator` is zero or `p_denominator > p_denominator`, if $a > b$, if $[a, b]$
415/// contains no graphic [`char`]s, or if $[a, b]$ contains only graphic [`char`]s.
416///
417/// # Examples
418/// ```
419/// use malachite_base::chars::random::graphic_weighted_random_char_inclusive_range;
420/// use malachite_base::random::EXAMPLE_SEED;
421///
422/// assert_eq!(
423///     graphic_weighted_random_char_inclusive_range(EXAMPLE_SEED, '\u{100}', '\u{3ff}', 10, 11)
424///         .take(30)
425///         .collect::<String>()
426///         .as_str(),
427///     "ǘɂŜȢΙƘƣʅΰǟ˳ˊȇ\u{31b}ʰɥΈ\u{324}\u{35a}Ϟ\u{367}\u{337}ƃ\u{342}ʌμƢϳϪǰ"
428/// )
429/// ```
430pub fn graphic_weighted_random_char_inclusive_range(
431    seed: Seed,
432    a: char,
433    b: char,
434    p_numerator: u64,
435    p_denominator: u64,
436) -> WeightedGraphicRandomCharRange {
437    assert!(a <= b, "a must be less than or equal to b. a: {a}, b: {b}");
438    let (graphic_chars, non_graphic_chars): (Vec<_>, Vec<_>) =
439        (a..=b).partition(|&c| char_is_graphic(c));
440    assert!(
441        !graphic_chars.is_empty(),
442        "The range {a:?}..={b:?} contains no graphic chars"
443    );
444    assert!(
445        !non_graphic_chars.is_empty(),
446        "The range {a:?}..={b:?} only contains graphic chars"
447    );
448    WeightedGraphicRandomCharRange {
449        xs: weighted_random_bools(seed.fork("xs"), p_numerator, p_denominator),
450        graphic: random_values_from_vec(seed.fork("graphic"), graphic_chars),
451        non_graphic: random_values_from_vec(seed.fork("non_graphic"), non_graphic_chars),
452    }
453}