use std::collections::HashMap;
use rand::prelude::SliceRandom;
use rand::rngs::StdRng;
const DEFAULT_TABLE_NAMES: &[&str] = &[
"Apollo",
"Athena",
"Hera",
"Hermes",
"Iris",
"Juno",
"Minerva",
"Neptune",
"Orion",
"Poseidon",
"Venus",
"Vulcan",
"Zeus",
"Artemis",
"Hestia",
"Demeter",
"Hades",
"Helios",
"Jason",
"Perseus",
"Andromeda",
"Cassiopeia",
"Draco",
"Lyra",
"Pegasus",
"Phoenix",
"Sagittarius",
"Taurus",
"Ursa",
"Vega",
"Altair",
"Antares",
"Betelgeuse",
"Cygnus",
"Rigel",
"Amethyst",
"Beryl",
"Citrine",
"Diamond",
"Emerald",
"Garnet",
"Jade",
"Kunzite",
"Lapis",
"Malachite",
"Onyx",
"Opal",
"Pearl",
"Quartz",
"Ruby",
"Sapphire",
"Topaz",
"Turquoise",
"Zircon",
"Oslo",
"Bergen",
"Helsinki",
"Reykjavik",
"Tallinn",
"Riga",
"Vilnius",
"Gdansk",
"Tromso",
"Uppsala",
];
pub struct TableNamer {
pool: Vec<String>,
cursor: usize,
table_names: HashMap<String, String>,
table_handles: HashMap<String, String>,
active: bool,
}
impl TableNamer {
pub fn new(active: bool, rng: &mut StdRng) -> Self {
let mut pool: Vec<String> = DEFAULT_TABLE_NAMES
.iter()
.map(|s| (*s).to_string())
.collect();
pool.shuffle(rng);
Self {
pool,
cursor: 0,
table_names: HashMap::new(),
table_handles: HashMap::new(),
active,
}
}
pub fn map_name(&mut self, name: &str) -> String {
if !self.active {
return name.to_string();
}
Self::lookup(
&mut self.table_names,
&mut self.pool,
&mut self.cursor,
name,
"Table",
)
}
pub fn map_handle(&mut self, handle: Option<&str>) -> Option<String> {
if !self.active {
return handle.map(|h| h.to_string());
}
handle.map(|h| {
Self::lookup(
&mut self.table_handles,
&mut self.pool,
&mut self.cursor,
h,
"Handle",
)
})
}
fn lookup(
table: &mut HashMap<String, String>,
pool: &mut [String],
cursor: &mut usize,
original: &str,
fallback_prefix: &str,
) -> String {
if let Some(v) = table.get(original) {
return v.clone();
}
let replacement = if *cursor < pool.len() {
let v = pool[*cursor].clone();
*cursor += 1;
v
} else {
format!("{fallback_prefix}{}", table.len() + 1)
};
table.insert(original.to_string(), replacement.clone());
replacement
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
fn rng() -> StdRng {
StdRng::seed_from_u64(3)
}
#[test]
fn inactive_is_identity() {
let mut t = TableNamer::new(false, &mut rng());
assert_eq!(t.map_name("Foo"), "Foo");
assert_eq!(t.map_handle(Some("bar")), Some("bar".to_string()));
assert_eq!(t.map_handle(None), None);
}
#[test]
fn same_input_is_stable() {
let mut t = TableNamer::new(true, &mut rng());
assert_eq!(t.map_name("Foo"), t.map_name("Foo"));
}
#[test]
fn distinct_inputs_are_distinct() {
let mut t = TableNamer::new(true, &mut rng());
assert_ne!(t.map_name("Foo"), t.map_name("Bar"));
}
}