use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
#[derive(Debug)]
pub struct DeterministicRng {
rng: ChaCha20Rng,
seed: u64,
fork_counter: u64,
}
impl DeterministicRng {
#[must_use]
pub fn new(seed: u64) -> Self {
let rng = ChaCha20Rng::seed_from_u64(seed);
let result = Self {
rng,
seed,
fork_counter: 0,
};
assert_eq!(result.seed, seed, "seed must be stored");
result
}
#[must_use]
pub fn seed(&self) -> u64 {
self.seed
}
pub fn next_float(&mut self) -> f64 {
let value = self.rng.gen::<f64>();
assert!((0.0..1.0).contains(&value), "float must be in [0, 1)");
value
}
pub fn next_u64(&mut self) -> u64 {
self.rng.gen()
}
pub fn next_int(&mut self, min: i64, max: i64) -> i64 {
assert!(min <= max, "min ({}) must be <= max ({})", min, max);
let value = self.rng.gen_range(min..=max);
assert!(value >= min && value <= max, "value must be in range");
value
}
pub fn next_usize(&mut self, min: usize, max: usize) -> usize {
assert!(min <= max, "min ({}) must be <= max ({})", min, max);
let value = self.rng.gen_range(min..=max);
assert!(value >= min && value <= max, "value must be in range");
value
}
pub fn next_bool(&mut self, probability: f64) -> bool {
assert!(
(0.0..=1.0).contains(&probability),
"probability must be in [0, 1], got {}",
probability
);
self.next_float() < probability
}
pub fn choose<'a, T>(&mut self, items: &'a [T]) -> &'a T {
assert!(!items.is_empty(), "cannot choose from empty slice");
let index = self.next_usize(0, items.len() - 1);
&items[index]
}
pub fn shuffle<T>(&mut self, items: &mut [T]) {
for i in (1..items.len()).rev() {
let j = self.next_usize(0, i);
items.swap(i, j);
}
}
pub fn fork(&mut self) -> Self {
let fork_seed = self.seed.wrapping_add(
self.fork_counter
.wrapping_add(1)
.wrapping_mul(0x9E3779B97F4A7C15),
);
self.fork_counter += 1;
Self::new(fork_seed)
}
pub fn next_bytes(&mut self, len: usize) -> Vec<u8> {
assert!(len <= 1_000_000, "len must be <= 1MB");
let mut bytes = vec![0u8; len];
self.rng.fill(&mut bytes[..]);
assert_eq!(bytes.len(), len, "must generate requested bytes");
bytes
}
}
impl Clone for DeterministicRng {
fn clone(&self) -> Self {
Self {
rng: self.rng.clone(),
seed: self.seed,
fork_counter: self.fork_counter,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_same_seed_same_sequence() {
let mut rng1 = DeterministicRng::new(12345);
let mut rng2 = DeterministicRng::new(12345);
for _ in 0..100 {
assert_eq!(rng1.next_float(), rng2.next_float());
}
}
#[test]
fn test_different_seeds_different_sequence() {
let mut rng1 = DeterministicRng::new(12345);
let mut rng2 = DeterministicRng::new(54321);
let differs = (0..10).any(|_| rng1.next_float() != rng2.next_float());
assert!(
differs,
"different seeds should produce different sequences"
);
}
#[test]
fn test_next_int_bounds() {
let mut rng = DeterministicRng::new(42);
for _ in 0..100 {
let val = rng.next_int(5, 10);
assert!((5..=10).contains(&val));
}
}
#[test]
fn test_next_bool_always_false() {
let mut rng = DeterministicRng::new(42);
for _ in 0..100 {
assert!(!rng.next_bool(0.0));
}
}
#[test]
fn test_next_bool_always_true() {
let mut rng = DeterministicRng::new(42);
for _ in 0..100 {
assert!(rng.next_bool(1.0));
}
}
#[test]
fn test_fork_independence() {
let mut rng = DeterministicRng::new(42);
let mut fork1 = rng.fork();
let mut fork2 = rng.fork();
assert_ne!(
fork1.seed(),
fork2.seed(),
"forks should have different seeds"
);
let fork1_vals: Vec<f64> = (0..5).map(|_| fork1.next_float()).collect();
let fork2_vals: Vec<f64> = (0..5).map(|_| fork2.next_float()).collect();
assert_ne!(
fork1_vals, fork2_vals,
"forks should have different sequences"
);
let _ = rng.next_float();
}
#[test]
fn test_choose() {
let mut rng = DeterministicRng::new(42);
let items = vec![1, 2, 3, 4, 5];
for _ in 0..100 {
let chosen = rng.choose(&items);
assert!(items.contains(chosen));
}
}
#[test]
fn test_shuffle() {
let mut rng = DeterministicRng::new(42);
let mut items = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let original = items.clone();
rng.shuffle(&mut items);
assert_ne!(items, original, "shuffle should change order");
items.sort();
assert_eq!(items, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}
#[test]
fn test_next_bytes() {
let mut rng = DeterministicRng::new(42);
let bytes = rng.next_bytes(32);
assert_eq!(bytes.len(), 32);
}
#[test]
#[should_panic(expected = "min (10) must be <= max (5)")]
fn test_next_int_invalid_range() {
let mut rng = DeterministicRng::new(42);
rng.next_int(10, 5);
}
#[test]
#[should_panic(expected = "probability must be in [0, 1]")]
fn test_next_bool_invalid_probability() {
let mut rng = DeterministicRng::new(42);
rng.next_bool(1.5);
}
#[test]
#[should_panic(expected = "cannot choose from empty slice")]
fn test_choose_empty() {
let mut rng = DeterministicRng::new(42);
let items: Vec<i32> = vec![];
rng.choose(&items);
}
}