Expose some more randomization utilities
This commit is contained in:
parent
70707f0b8e
commit
3530038ca4
42
src/util.rs
42
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()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue