claudiofsr_lib/
random.rs

1use crate::MyResult;
2use std::{
3    cell::RefCell,
4    time::{SystemTime, UNIX_EPOCH},
5};
6// use std::hash::{BuildHasher, Hasher, RandomState};
7
8/// A simple, fast Pseudo-Random Number Generator (PRNG) using the xorshift* algorithm.
9/// This provides better statistical properties than a simple Linear Congruential Generator (LCG).
10pub struct XorShiftRng {
11    state: u64,
12}
13
14impl XorShiftRng {
15    // Constructor to create a new instance with a seed
16    fn new(seed: u64) -> Self {
17        XorShiftRng { state: seed }
18    }
19
20    /// Generates the next random u64 number in the sequence.
21    fn generate(&mut self) -> u64 {
22        let mut x = self.state;
23        x ^= x >> 12; // a
24        x ^= x << 25; // b
25        x ^= x >> 27; // c
26        self.state = x;
27        x.wrapping_mul(0x2545F4914F6CDD1D)
28    }
29}
30
31/// Provides a seed based on the system's high-resolution clock.
32/// This is a good source for a non-deterministic seed.
33fn get_seed() -> u64 {
34    let nanos = SystemTime::now()
35        .duration_since(UNIX_EPOCH)
36        .expect("Time went backwards, system clock is unreliable")
37        .as_nanos() as u64;
38
39    // Ensure the seed is never zero, which is invalid for XorShiftRng.
40    if nanos == 0 { 1 } else { nanos }
41}
42
43// Use a thread-local static RNG. This is the most important best practice.
44// 1. `thread_local!`: Creates a variable that is unique to each thread, avoiding expensive locking.
45// 2. `RefCell`: Allows us to get a mutable reference (`&mut`) to the RNG from an immutable context.
46// This ensures that we use ONE generator per thread and properly advance its state,
47// instead of creating and seeding a new one for every random number.
48thread_local!(
49    static THREAD_RNG: RefCell<XorShiftRng> = RefCell::new(XorShiftRng::new(get_seed()));
50);
51
52/// Generate random numbers without external dependencies
53pub fn rand() -> u64 {
54    // RandomState::new().build_hasher().finish()
55    THREAD_RNG.with(|rng| rng.borrow_mut().generate())
56}
57
58/// Generates a random integer within a given range `[min, max]` (inclusive).
59///
60/// This implementation is carefully designed to avoid "modulo bias". It does this using
61/// rejection sampling. In the astronomically rare case that it fails to find an
62/// unbiased number after a set number of retries, it falls back to a potentially
63/// biased result to guarantee termination.
64///
65/// ### Arguments
66/// * `min` - The lower bound of the range (inclusive).
67/// * `max` - The upper bound of the range (inclusive).
68///
69/// ### Errors
70/// Returns an error if `min > max`.
71pub fn random_in_range(min: u64, max: u64) -> MyResult<u64> {
72    if min > max {
73        let msg = format!("min ({min}) must be less than or equal to max ({max})");
74        return Err(msg.into());
75    }
76
77    // The number of possible outcomes in the range [min, max].
78    // `wrapping_add(1)` correctly handles the case where the range is the full `u64`.
79    // In that case, `max - min` is `u64::MAX`, and `wrapping_add(1)` results in 0.
80    let range_size = max.wrapping_sub(min).wrapping_add(1);
81
82    // If range_size is 0, it signifies the full u64 range was requested.
83    if range_size == 0 {
84        return Ok(rand());
85    }
86
87    // To avoid modulo bias, we find the largest multiple of `range_size` that
88    // fits in a u64. Any random number generated above this threshold would,
89    // if mapped with modulo, create an unfair distribution.
90    let rejection_threshold = (u64::MAX / range_size) * range_size;
91
92    // The number of attempts before falling back to a biased result.
93    // The probability of exceeding this is negligible.
94    const MAX_RETRIES: u32 = 100;
95
96    for _ in 0..MAX_RETRIES {
97        let value = rand();
98        // If the value is within the unbiased zone, we use it. This is the common path.
99        if value < rejection_threshold {
100            return Ok(min + (value % range_size));
101        }
102        // Otherwise, we "reject" the sample and try again.
103    }
104
105    // Fallback: If we exhausted all retries (extremely unlikely), we return a
106    // result that may be slightly biased. This guarantees function termination.
107    Ok(min + (rand() % range_size))
108}
109
110/// A trait for shuffling mutable slices in place.
111///
112/// This trait provides a `shuffle` method that shuffles the elements of any mutable
113/// slice `&mut [T]` using the modern Fisher-Yates algorithm (Durstenfeld's variant).
114pub trait Shuffle {
115    /// Shuffles the elements of the slice in place.
116    ///
117    /// This algorithm iterates from the end of the slice to the beginning, swapping
118    /// each element with a randomly selected element from the part of the slice
119    /// that has not yet been shuffled.
120    ///
121    /// ### Examples
122    ///
123    /// ```
124    /// use claudiofsr_lib::Shuffle;
125    ///
126    /// let mut strings = vec!["abc", "foo", "bar", "baz", "mm nn", "zzz"];
127    /// strings.shuffle();
128    /// println!("shuffled strings: {:?}", strings);
129    ///
130    /// let mut integers: Vec<u32> = (1..=20).collect();
131    /// integers.shuffle();
132    /// println!("shuffled integers: {:?}", integers);
133    /// ```
134    ///
135    /// ### Links
136    /// - <https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle>
137    /// - <https://stackoverflow.com/questions/26033976/how-do-i-create-a-vec-from-a-range-and-shuffle-it>
138    fn shuffle(&mut self);
139}
140
141// Implement the Shuffle trait for any mutable slice of any type T.
142impl<T> Shuffle for &mut [T] {
143    fn shuffle(&mut self) {
144        let len = self.len();
145        // The loop `(1..len).rev()` handles slices of length 0 or 1 gracefully
146        // by not executing, so a separate `if len > 1` check is not needed.
147        for i in (1..len).rev() {
148            // The algorithm swaps the element at index `i` with an element at a
149            // randomly chosen index `j` from the range `[0, i]` (inclusive).
150
151            // Generate a random index `j` such that `0 <= j <= i`.
152            // The call to `.unwrap()` here is guaranteed to be safe and will never panic.
153            // This is because `random_in_range(min, max)` only returns an `Err`
154            // if `min > max`. In this loop, the arguments are `random_in_range(0, i)`,
155            // and the loop invariant ensures that `i` is always >= 1.
156            // Therefore, `0 <= i` is always true, the function will always return `Ok(...)`,
157            // and the unwrap is safe.
158            let j = random_in_range(0, i as u64).unwrap() as usize;
159
160            self.swap(i, j);
161        }
162    }
163}
164
165// Implement the Shuffle trait for Vec<T> by delegating to its slice.
166impl<T> Shuffle for Vec<T> {
167    fn shuffle(&mut self) {
168        self.as_mut_slice().shuffle();
169    }
170}
171
172#[cfg(test)]
173mod test_random {
174    use super::*; // Import everything from the parent module
175    use std::collections::HashSet; // Import HashSet for tests
176
177    #[test]
178    /// Tests that the thread-local RNG is stateful and produces different numbers.
179    ///
180    /// `cargo test -- --show-output gen_random`
181    fn gen_random() {
182        let mut numbers = HashSet::new();
183
184        // Generate and print some random numbers
185        for n in 0..1000 {
186            let random = rand();
187            println!("random number {n:3}: {random}");
188
189            // Check for a specific one.
190            if !numbers.insert(random) {
191                eprintln!("Error: {random}");
192                panic!("Not random!");
193            }
194        }
195
196        // It's astronomically unlikely that 1000 random u64s will have a collision.
197        // A failure here would indicate the RNG state is not advancing.
198        println!("numbers: {numbers:#?}");
199        assert_eq!(numbers.len(), 1000);
200    }
201
202    #[test]
203    /// Verifies that the shuffle function correctly permutes all elements.
204    ///
205    /// `cargo test -- --show-output shuffle_preserves_elements`
206    fn shuffle_preserves_elements() {
207        let mut original: Vec<u32> = (1..=100).collect();
208        let mut shuffled = original.clone();
209
210        shuffled.shuffle(); // Using the trait method
211
212        println!("original: {original:?}");
213        println!("shuffled: {shuffled:?}");
214
215        // Basic checks: length should be the same, and order should be different.
216        assert_eq!(original.len(), shuffled.len());
217        assert_ne!(
218            original, shuffled,
219            "Shuffle should change the order (highly likely)."
220        );
221
222        // The most important check: a shuffled vector must contain exactly the same elements.
223        // We can verify this by sorting both and comparing.
224        original.sort();
225        shuffled.sort();
226        assert_eq!(
227            original, shuffled,
228            "A valid shuffle must preserve all original elements."
229        );
230    }
231
232    #[test]
233    /// Tests that generated values fall within the specified inclusive range.
234    fn random_in_range_bounds() -> MyResult<()> {
235        let min = 100;
236        let max = 200;
237        for _ in 0..10_000 {
238            let val = random_in_range(min, max)?;
239            assert!(
240                val >= min && val <= max,
241                "Value {val} is outside the range [{min}, {max}]"
242            );
243        }
244        Ok(())
245    }
246
247    #[test]
248    /// `cargo test -- --show-output random_integers`
249    ///
250    /// <https://stackoverflow.com/questions/48218459/how-do-i-generate-a-vector-of-random-numbers-in-a-range>
251    fn random_integers() -> MyResult<()> {
252        // Example: Get a random integer value in the range 1 to 20:
253        let value: u64 = random_in_range(1, 20)?;
254
255        println!("integer: {value:?}");
256
257        // Generate a vector of 100 64-bit integer values in the range from 1 to 20,
258        // allowing duplicates:
259
260        let integers: Vec<u64> = (0..100)
261            .map(|_| random_in_range(1, 20))
262            .collect::<Result<Vec<u64>, _>>()?;
263
264        println!("integers: {integers:?}");
265
266        let condition_a = integers.iter().min() >= Some(&1);
267        let condition_b = integers.iter().max() <= Some(&20);
268
269        assert!(condition_a);
270        assert!(condition_b);
271        assert_eq!(integers.len(), 100);
272
273        Ok(())
274    }
275
276    #[test]
277    /// Ensures that an invalid range (min > max) correctly returns an error.
278    ///
279    /// `cargo test -- --show-output random_in_range_errors_on_invalid_range`
280    fn random_in_range_errors_on_invalid_range() -> MyResult<()> {
281        let result = random_in_range(21, 20).map_err(|err| {
282            eprintln!("{err}");
283            err
284        });
285        assert!(result.is_err());
286
287        // We can also check the specific error message for a more robust test.
288        assert_eq!(
289            result.unwrap_err().to_string(),
290            "min (21) must be less than or equal to max (20)"
291        );
292
293        Ok(())
294    }
295
296    #[test]
297    /// Tests the `shuffle` method on empty and single-element vectors.
298    fn shuffle_empty_and_single_element() {
299        let mut empty_vec: Vec<u32> = Vec::new();
300        empty_vec.shuffle();
301        assert!(empty_vec.is_empty());
302
303        let mut single_vec = vec![42];
304        single_vec.shuffle();
305        assert_eq!(single_vec, vec![42]); // Single element remains unchanged
306    }
307
308    #[test]
309    /// Tests that calling shuffle multiple times produces different results (high probability).
310    fn shuffle_multiple_times() {
311        let original: Vec<u32> = (1..=10).collect();
312        let mut first_shuffle = original.clone();
313        first_shuffle.shuffle();
314
315        let mut second_shuffle = original.clone();
316        second_shuffle.shuffle();
317
318        // While not guaranteed, it's astronomically unlikely that two independent shuffles
319        // of the same initial 10 elements will produce the exact same sequence.
320        assert_ne!(first_shuffle, original);
321        assert_ne!(second_shuffle, original);
322        assert_ne!(
323            first_shuffle, second_shuffle,
324            "Two shuffles produced the same result, which is highly unlikely."
325        );
326    }
327}