From 3530038ca45117de4b4ee8284f00d6bae3e6bc32 Mon Sep 17 00:00:00 2001 From: Emi Simpson Date: Mon, 25 Oct 2021 12:16:57 -0400 Subject: [PATCH] Expose some more randomization utilities --- src/util.rs | 42 +++++++++++++++++++++++++++++++++ src/weighted_table.rs | 54 +++++++++++++++---------------------------- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/src/util.rs b/src/util.rs index 2e93d75..8b30918 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,21 @@ +use time::{Date, Month, OffsetDateTime}; + +/// The start of the COVID-19 lockdowns +/// +/// This is used as an epoch in order to convert from a given date to an integer seed. This is +/// specified as part of the algorithm for randomly selecting from a weighted list. +pub const COVID_EPOCH: Date = match Date::from_calendar_date(2020, Month::January, 26) { + Ok(d) => d, + Err(_) => Date::MIN, // This never runs, but we can't unwrap, so this is what we're stuck with +}; + +/// A state to use as an initial state before seeding +/// +/// Think of this as a ::new() method for a pcg64 generator. Subsiquent calls should be +/// made to the [`pcg64_seed()`] methods or the [`seed_with_date()`]/[`seed_with_today()`] +/// methods +pub const INITIAL_STATE: u128 = 1312_1312_1312; + pub fn pcg64(state: &mut u128) -> u64 { let mut x = *state; pcg64_iterstate(state); @@ -18,3 +36,27 @@ pub fn pcg64_seed(state: &mut u128, bytes: &[u8]) { } pcg64_iterstate(state); } + +/// Seed the generator with a given [`Date`] object +pub fn seed_with_date(state: &mut u128, date: Date) { + pcg64_seed( + state, + &( + (date - COVID_EPOCH) + .whole_days() + as u32 + ).to_le_bytes() + ) +} + +/// Seed the generator with the [`Date`] object representing today's date +/// +/// Uses the system's local time, or falls back to UTC +pub fn seed_with_today(state: &mut u128) { + seed_with_date( + state, + OffsetDateTime::now_local() + .unwrap_or_else(|_| OffsetDateTime::now_utc()) + .date() + ) +} diff --git a/src/weighted_table.rs b/src/weighted_table.rs index dd03535..55a730a 100644 --- a/src/weighted_table.rs +++ b/src/weighted_table.rs @@ -1,22 +1,10 @@ -use crate::{ - user_preferences::ParseError, - util::{pcg64, pcg64_seed}, -}; +use crate::{user_preferences::ParseError, util::{self, pcg64, pcg64_seed, seed_with_date, seed_with_today}}; use std::{collections::BTreeMap, hash::Hash}; use std::fmt::Debug; use std::cmp::Eq; -use time::{Date, Month, OffsetDateTime}; - -/// The start of the COVID-19 lockdowns -/// -/// This is used as an epoch in order to convert from a given date to an integer seed. This is -/// specified as part of the algorithm for randomly selecting from a weighted list. -pub const COVID_EPOCH: Date = match Date::from_calendar_date(2020, Month::January, 26) { - Ok(d) => d, - Err(_) => Date::MIN, // This never runs, but we can't unwrap, so this is what we're stuck with -}; +use time::Date; /// A list of pronouns and their associated weights, used for random selection /// @@ -82,41 +70,37 @@ impl WeightedTable { /// /// The date is generated for the system's time and timezone pub fn select_today(&self, seed: &[u8]) -> Loot { - self.select_on_date( - seed, - OffsetDateTime::now_local() - .unwrap_or_else(|_| OffsetDateTime::now_utc()) - .date() - ) + let mut generator = util::INITIAL_STATE; + + seed_with_today(&mut generator); + pcg64_seed(&mut generator, seed); + + self.select(&mut generator) } /// Randomly select a pronoun set for a given date and name. /// /// Is a wrapper for calling [`WeightedTable::select`] with the given date mixed into the seed. pub fn select_on_date(&self, seed: &[u8], date: Date) -> Loot { - let mut new_seed: Vec = Vec::with_capacity(seed.len() + 4); - new_seed.extend( - ( - (date - COVID_EPOCH) - .whole_days() - as u32 - ).to_le_bytes() - ); - new_seed.extend(seed); - self.select(&new_seed) + let mut generator = util::INITIAL_STATE; + + seed_with_date(&mut generator, date); + pcg64_seed(&mut generator, seed); + + self.select(&mut generator) } - /// Randomly select a pronoun set for a given seed + /// Randomly select a pronoun set from a given pre-seeded genenator + /// + /// You're probably looking for [`select_today()`] or [`select_on_date()`] /// /// This function is *pure*, and any randomness is produced internally using PRNG seeded with /// the given date and seed. That is to say, for any given seed, this table must always /// produce the same pronoun set. - pub fn select(&self, seed: &[u8]) -> Loot { + pub fn select(&self, generator: &mut u128) -> Loot { let (rollable_table, sum_weights) = self.rollable_table(); - let mut generator: u128 = 131213121312; - pcg64_seed(&mut generator, seed); - let random = pcg64(&mut generator) % sum_weights; + let random = pcg64(generator) % sum_weights; rollable_table.iter() .filter(|(weight, _)| random < *weight)