use super::rng;
use num_bigint::BigUint;
use num_traits::ToPrimitive;
pub const MAX_RANDC_WIDTH: u32 = 16;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RandcState {
pub live: bool,
pub fingerprint: u64,
pub remaining: Vec<u32>,
}
pub fn fingerprint_of(bytes: &[u8]) -> u64 {
let mut h = 0xcbf2_9ce4_8422_2325u64;
for &b in bytes {
h ^= u64::from(b);
h = h.wrapping_mul(0x0100_0000_01b3);
}
h
}
fn shuffle(values: &mut [u32], seed: &mut u64) {
for i in (1..values.len()).rev() {
let j = rng::below(seed, &BigUint::from(i as u64 + 1))
.to_usize()
.expect("a draw below a usize bound fits a usize");
values.swap(i, j);
}
}
fn reshuffle(state: &mut RandcState, fingerprint: u64, mut legal: Vec<u32>, seed: &mut u64) {
shuffle(&mut legal, seed);
state.live = true;
state.fingerprint = fingerprint;
state.remaining = legal;
}
pub fn pick(
state: &mut RandcState,
fingerprint: u64,
legal: &mut dyn FnMut() -> Vec<u32>,
feasible: &mut dyn FnMut(u32) -> bool,
seed: &mut u64,
) -> Option<u32> {
let mut fresh = false;
if !state.live || state.fingerprint != fingerprint {
reshuffle(state, fingerprint, legal(), seed);
fresh = true;
}
loop {
if let Some(i) = state.remaining.iter().position(|&v| feasible(v)) {
return Some(state.remaining.remove(i));
}
if fresh {
return None;
}
reshuffle(state, fingerprint, legal(), seed);
fresh = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn draw(state: &mut RandcState, legal: &[u32], seed: &mut u64) -> u32 {
pick(state, 1, &mut || legal.to_vec(), &mut |_| true, seed).expect("a value")
}
#[test]
fn a_cycle_visits_every_value_once_then_starts_again() {
let mut st = RandcState::default();
let mut seed = 7;
let legal = [0, 1, 2, 3];
for _ in 0..50 {
let mut cycle: Vec<u32> = (0..4).map(|_| draw(&mut st, &legal, &mut seed)).collect();
cycle.sort_unstable();
assert_eq!(cycle, vec![0, 1, 2, 3]);
}
}
#[test]
fn permutations_differ_between_cycles() {
let mut st = RandcState::default();
let mut seed = 99;
let legal: Vec<u32> = (0..16).collect();
let cycles: Vec<Vec<u32>> = (0..20)
.map(|_| (0..16).map(|_| draw(&mut st, &legal, &mut seed)).collect())
.collect();
let distinct: std::collections::BTreeSet<_> = cycles.iter().collect();
assert!(distinct.len() > 15, "{} distinct of 20", distinct.len());
}
#[test]
fn every_position_of_the_permutation_is_uniform() {
let mut counts = [0u32; 5];
let mut seed = 3;
for _ in 0..5000 {
let mut st = RandcState::default();
counts[draw(&mut st, &[0, 1, 2, 3, 4], &mut seed) as usize] += 1;
}
for c in counts {
assert!((880..1120).contains(&c), "{counts:?}");
}
}
#[test]
fn a_changed_fingerprint_starts_a_new_permutation() {
let mut st = RandcState::default();
let mut seed = 5;
let first = pick(
&mut st,
1,
&mut || vec![0, 1, 2, 3],
&mut |_| true,
&mut seed,
)
.unwrap();
assert_eq!(st.remaining.len(), 3);
let mut seen = std::collections::BTreeSet::new();
for _ in 0..2 {
seen.insert(pick(&mut st, 2, &mut || vec![0, 1], &mut |_| true, &mut seed).unwrap());
}
assert_eq!(seen, [0, 1].into_iter().collect());
assert_eq!(st.fingerprint, 2);
assert!(first < 4);
}
#[test]
fn an_infeasible_value_is_skipped_not_consumed() {
let mut st = RandcState::default();
let mut seed = 11;
let a = pick(
&mut st,
1,
&mut || vec![0, 1, 2],
&mut |v| v != 2,
&mut seed,
)
.unwrap();
let b = pick(
&mut st,
1,
&mut || vec![0, 1, 2],
&mut |v| v != 2,
&mut seed,
)
.unwrap();
assert_ne!(a, b);
assert!(a != 2 && b != 2);
assert_eq!(st.remaining, vec![2], "2 stays in the permutation");
let c = pick(&mut st, 1, &mut || vec![0, 1, 2], &mut |_| true, &mut seed).unwrap();
assert_eq!(c, 2, "and is produced once it is feasible");
}
#[test]
fn nothing_feasible_in_the_rest_of_the_cycle_draws_a_new_one() {
let mut st = RandcState::default();
let mut seed = 13;
assert!(pick(&mut st, 1, &mut || vec![0, 1], &mut |v| v == 0, &mut seed).is_some());
let v = pick(&mut st, 1, &mut || vec![0, 1], &mut |v| v == 0, &mut seed).unwrap();
assert_eq!(v, 0);
}
#[test]
fn no_feasible_value_at_all_is_none_and_the_state_stays_usable() {
let mut st = RandcState::default();
let mut seed = 17;
assert_eq!(
pick(&mut st, 1, &mut || vec![0, 1], &mut |_| false, &mut seed),
None
);
assert!(pick(&mut st, 1, &mut || vec![0, 1], &mut |_| true, &mut seed).is_some());
}
#[test]
fn the_draws_are_a_pure_function_of_the_seed() {
let run = |s0: u64| {
let mut st = RandcState::default();
let mut seed = s0;
(0..40)
.map(|_| draw(&mut st, &[0, 1, 2, 3, 4, 5], &mut seed))
.collect::<Vec<_>>()
};
assert_eq!(run(1), run(1));
assert_ne!(run(1), run(2));
}
#[test]
fn fingerprints_tell_different_byte_strings_apart() {
assert_eq!(fingerprint_of(b"abc"), fingerprint_of(b"abc"));
assert_ne!(fingerprint_of(b"abc"), fingerprint_of(b"abd"));
assert_ne!(fingerprint_of(b""), fingerprint_of(b"\0"));
}
}