Expose some more randomization utilities

This commit is contained in:
Emi Simpson 2021-10-25 12:16:57 -04:00
parent 70707f0b8e
commit 3530038ca4
Signed by: Emi
GPG key ID: A12F2C2FFDC3D847
2 changed files with 61 additions and 35 deletions

View file

@ -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()
)
}

View file

@ -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<Loot: Eq + Ord + Hash + Copy + Debug> WeightedTable<Loot> {
///
/// 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<u8> = 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)