Time and IDs

Seeded random

Random that repeats for a seed: daily puzzles, fair draws, A/B groups and repeatable tests.

Version 1.0.0 · 5 functions · 2 crates

#random#games#testing#experiments

Functions

What use seeded.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use seeded_floats, seeded_int from seeded (Quillo 0.6).

seeded_floats(seed: str, count: int) → list

count numbers from 0 (included) to 1, the same for the same seed.

seeded_int(seed: str, lo: int, hi: int) → intor an error

A number from lo to hi, both included, the same for the same seed.

seeded_shuffle(items: list, seed: str) → list

items shuffled, the same way for the same seed.

seeded_pick(items: list, n: int, seed: str) → list

n of items, none twice, the same ones for the same seed: a fair draw anyone can check.

bucket(id: str, buckets: int) → int

Which of buckets groups (0 to buckets - 1) id is in, always the same one: A/B tests, gradual roll-outs.

Get it

$ quillo install seeded               # seeded.qwer into this folder$ quillo install seeded --example     # and its example, seeded.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/seeded.qwer$ curl -O https://quillolang.org/plugins/files/seeded.qwe

Then, in your program, all of it or only what you call:

main.qwe
use seeded.qweruse seeded_floats, seeded_int from seeded      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
rand = "0.8"rand_chacha = "0.3"

Example

seeded.qwe
// Today's puzzle is the same for everyone, and a user stays in their test group.////   quillo run seeded.qwe      then open http://localhost:8080/todayuse "seeded.qwer"let words = ["quill", "route", "state", "patch", "token", "frame", "query", "proxy"]route GET /today {    let day = date.format(date.today(), "yyyy-MM-dd")    { day, word: first(seeded_pick(words, 1, day)), again: first(seeded_pick(words, 1, day)) }}route GET /fixed = { int: seeded_int("draw-2026", 1, 100)?, order: seeded_shuffle([1, 2, 3, 4, 5], "round-1"), group: bucket("user-42", 2) }
$ quillo run seeded.qwe$ curl 'localhost:8080/today'$ curl 'localhost:8080/fixed'

Source

seeded.qwer
//! Random that repeats: the same seed gives the same numbers, shuffles and picks, for daily puzzles, A/B groups, fair draws and tests.//! version: 1.0.0//!//! [dependencies]//! rand = "0.8"//! rand_chacha = "0.3"use rand::seq::SliceRandom;use rand::{Rng, SeedableRng};use rand_chacha::ChaCha8Rng;/// FNV-1a: the same number for the same seed text, on every machine and Rust version.fn rng(seed: &str) -> ChaCha8Rng {    let mut h: u64 = 0xcbf29ce484222325;    for b in seed.as_bytes() {        h ^= *b as u64;        h = h.wrapping_mul(0x100000001b3);    }    ChaCha8Rng::seed_from_u64(h)}/// `count` numbers from 0 (included) to 1, the same for the same `seed`.pub fn seeded_floats(seed: &str, count: i64) -> Vec<f64> {    let mut r = rng(seed);    (0..count.clamp(0, 1_000_000)).map(|_| r.gen::<f64>()).collect()}/// A number from `lo` to `hi`, both included, the same for the same `seed`.pub fn seeded_int(seed: &str, lo: i64, hi: i64) -> Result<i64, String> {    if lo > hi { return Err("lo is above hi".into()); }    Ok(rng(seed).gen_range(lo..=hi))}/// `items` shuffled, the same way for the same `seed`.pub fn seeded_shuffle(items: Vec<serde_json::Value>, seed: &str) -> Vec<serde_json::Value> {    let mut v = items;    v.shuffle(&mut rng(seed));    v}/// `n` of `items`, none twice, the same ones for the same `seed`: a fair draw anyone can check.pub fn seeded_pick(items: Vec<serde_json::Value>, n: i64, seed: &str) -> Vec<serde_json::Value> {    items.choose_multiple(&mut rng(seed), n.max(0) as usize).cloned().collect()}/// Which of `buckets` groups (0 to buckets - 1) `id` is in, always the same one: A/B tests, gradual roll-outs.pub fn bucket(id: &str, buckets: i64) -> i64 {    rng(id).gen_range(0..buckets.max(1))}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On Seeded random: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.