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}