1use crate::sampling::alias::AliasTableU64;
25use std::sync::OnceLock;
26
27static FEMALE_FIRSTNAMES_CSV: &str = include_str!("../data/census/female_firstnames.csv");
32static MALE_FIRSTNAMES_CSV: &str = include_str!("../data/census/male_firstnames.csv");
33static STATES_CSV: &str = include_str!("../data/census/census_state_abbrev.csv");
34static COUNTRIES_CSV: &str = include_str!("../data/census/countries.csv");
35static NATIONALITIES_CSV: &str = include_str!("../data/census/nationalities.csv");
36
37fn parse_name_weight_csv(csv: &str) -> (Vec<String>, Vec<f64>) {
43 let mut names = Vec::new();
44 let mut weights = Vec::new();
45 for line in csv.lines().skip(1) {
46 let parts: Vec<&str> = line.split(',').collect();
47 if parts.len() >= 2 {
48 let name = parts[0].trim().to_string();
49 if let Ok(w) = parts[1].trim().parse::<f64>()
50 && !name.is_empty()
51 && w > 0.0
52 {
53 names.push(name);
54 weights.push(w);
55 }
56 }
57 }
58 (names, weights)
59}
60
61fn parse_single_column_csv(csv: &str) -> Vec<String> {
63 csv.lines()
64 .skip(1)
65 .map(|l| l.trim().to_string())
66 .filter(|l| !l.is_empty())
67 .collect()
68}
69
70fn parse_code_name_csv(csv: &str) -> Vec<(String, String)> {
72 csv.lines()
73 .skip(1)
74 .filter_map(|l| {
75 let parts: Vec<&str> = l.split(',').collect();
76 if parts.len() >= 2 {
77 Some((parts[0].trim().to_string(), parts[1].trim().to_string()))
78 } else {
79 None
80 }
81 })
82 .collect()
83}
84
85pub struct WeightedNameSampler {
91 names: Vec<String>,
92 table: AliasTableU64,
93}
94
95impl WeightedNameSampler {
96 fn new(names: Vec<String>, weights: Vec<f64>) -> Self {
97 let table = AliasTableU64::from_weights(&weights);
98 Self { names, table }
99 }
100
101 fn sample(&self, input: u64) -> &str {
102 let idx = self.table.sample(input) as usize;
103 &self.names[idx]
104 }
105}
106
107pub struct UniformNameSampler {
109 names: Vec<String>,
110}
111
112impl UniformNameSampler {
113 fn new(names: Vec<String>) -> Self {
114 Self { names }
115 }
116
117 fn sample(&self, input: u64) -> &str {
118 let idx = (input as usize) % self.names.len();
119 &self.names[idx]
120 }
121}
122
123fn female_first_names() -> &'static WeightedNameSampler {
130 static CELL: OnceLock<WeightedNameSampler> = OnceLock::new();
131 CELL.get_or_init(|| {
132 let (names, weights) = parse_name_weight_csv(FEMALE_FIRSTNAMES_CSV);
133 WeightedNameSampler::new(names, weights)
134 })
135}
136
137fn male_first_names() -> &'static WeightedNameSampler {
138 static CELL: OnceLock<WeightedNameSampler> = OnceLock::new();
139 CELL.get_or_init(|| {
140 let (names, weights) = parse_name_weight_csv(MALE_FIRSTNAMES_CSV);
141 WeightedNameSampler::new(names, weights)
142 })
143}
144
145fn state_codes_data() -> &'static UniformNameSampler {
146 static CELL: OnceLock<UniformNameSampler> = OnceLock::new();
147 CELL.get_or_init(|| UniformNameSampler::new(parse_single_column_csv(STATES_CSV)))
148}
149
150fn country_names_data() -> &'static UniformNameSampler {
151 static CELL: OnceLock<UniformNameSampler> = OnceLock::new();
152 CELL.get_or_init(|| {
153 let pairs = parse_code_name_csv(COUNTRIES_CSV);
154 UniformNameSampler::new(pairs.into_iter().map(|(_, name)| name).collect())
155 })
156}
157
158fn country_codes_data() -> &'static UniformNameSampler {
159 static CELL: OnceLock<UniformNameSampler> = OnceLock::new();
160 CELL.get_or_init(|| {
161 let pairs = parse_code_name_csv(COUNTRIES_CSV);
162 UniformNameSampler::new(pairs.into_iter().map(|(code, _)| code).collect())
163 })
164}
165
166fn nationalities_data() -> &'static UniformNameSampler {
167 static CELL: OnceLock<UniformNameSampler> = OnceLock::new();
168 CELL.get_or_init(|| UniformNameSampler::new(parse_single_column_csv(NATIONALITIES_CSV)))
169}
170
171fn last_names_data() -> &'static UniformNameSampler {
172 static CELL: OnceLock<UniformNameSampler> = OnceLock::new();
173 CELL.get_or_init(|| {
174 UniformNameSampler::new(
175 crate::random::LASTNAMES
176 .lines()
177 .filter(|l| !l.is_empty())
178 .map(|l| l.to_string())
179 .collect(),
180 )
181 })
182}
183
184#[polydat::polydat_node(category = RealData)]
191fn first_names(input: u64) -> String {
192 female_first_names().sample(input).to_string()
193}
194
195#[polydat::polydat_node(category = RealData)]
198fn first_names_male(input: u64) -> String {
199 male_first_names().sample(input).to_string()
200}
201
202#[polydat::polydat_node(category = RealData)]
205fn state_codes(input: u64) -> String {
206 state_codes_data().sample(input).to_string()
207}
208
209#[polydat::polydat_node(category = RealData)]
212fn country_names(input: u64) -> String {
213 country_names_data().sample(input).to_string()
214}
215
216#[polydat::polydat_node(category = RealData)]
219fn country_codes(input: u64) -> String {
220 country_codes_data().sample(input).to_string()
221}
222
223#[polydat::polydat_node(category = RealData)]
226fn nationalities(input: u64) -> String {
227 nationalities_data().sample(input).to_string()
228}
229
230#[polydat::polydat_node(category = RealData)]
236fn full_names(input: u64) -> String {
237 use xxhash_rust::xxh3::xxh3_64;
238 let h2 = xxh3_64(&input.to_le_bytes());
239 let h3 = xxh3_64(&h2.to_le_bytes());
240 let first = if h2 & 1 == 0 {
241 female_first_names().sample(h2)
242 } else {
243 male_first_names().sample(h2)
244 };
245 let last_name = last_names_data().sample(h3);
246 format!("{first} {last_name}")
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use polydat::ast::{PolydatNode, Value};
253 use xxhash_rust::xxh3::xxh3_64;
254
255 #[test]
256 fn first_names_female() {
257 let node = FirstNames::new();
258 let mut out = [Value::None];
259 let h = xxh3_64(&42u64.to_le_bytes());
260 node.eval(&[Value::U64(h)], &mut out);
261 let name = out[0].as_str();
262 assert!(!name.is_empty());
263 assert!(name.chars().all(|c| c.is_alphabetic()));
264 }
265
266 #[test]
267 fn first_names_male() {
268 let node = FirstNamesMale::new();
269 let mut out = [Value::None];
270 let h = xxh3_64(&42u64.to_le_bytes());
271 node.eval(&[Value::U64(h)], &mut out);
272 assert!(!out[0].as_str().is_empty());
273 }
274
275 #[test]
276 fn first_names_weighted() {
277 let node = FirstNames::new();
279 let mut mary_count = 0;
280 let mut out = [Value::None];
281 for i in 0..10_000u64 {
282 let h = xxh3_64(&i.to_le_bytes());
283 node.eval(&[Value::U64(h)], &mut out);
284 if out[0].as_str() == "Mary" {
285 mary_count += 1;
286 }
287 }
288 assert!(
289 mary_count > 50,
290 "Mary should appear frequently, got {mary_count}"
291 );
292 }
293
294 #[test]
295 fn state_codes_valid() {
296 let node = StateCodes::new();
297 let mut out = [Value::None];
298 for i in 0..100u64 {
299 node.eval(&[Value::U64(i)], &mut out);
300 let code = out[0].as_str();
301 assert_eq!(code.len(), 2, "state code should be 2 chars: {code}");
302 assert!(code.chars().all(|c| c.is_ascii_uppercase()));
303 }
304 }
305
306 #[test]
307 fn country_names_nonempty() {
308 let node = CountryNames::new();
309 let mut out = [Value::None];
310 for i in 0..100u64 {
311 node.eval(&[Value::U64(i)], &mut out);
312 assert!(!out[0].as_str().is_empty());
313 }
314 }
315
316 #[test]
317 fn country_codes_two_char() {
318 let node = CountryCodes::new();
319 let mut out = [Value::None];
320 for i in 0..100u64 {
321 node.eval(&[Value::U64(i)], &mut out);
322 assert_eq!(out[0].as_str().len(), 2);
323 }
324 }
325
326 #[test]
327 fn nationalities_nonempty() {
328 let node = Nationalities::new();
329 let mut out = [Value::None];
330 for i in 0..100u64 {
331 node.eval(&[Value::U64(i)], &mut out);
332 assert!(!out[0].as_str().is_empty());
333 }
334 }
335
336 #[test]
337 fn full_names_format() {
338 let node = FullNames::new();
339 let mut out = [Value::None];
340 let h = xxh3_64(&42u64.to_le_bytes());
341 node.eval(&[Value::U64(h)], &mut out);
342 let name = out[0].as_str();
343 assert!(name.contains(' '), "full name should have a space: {name}");
344 assert!(name.len() > 3, "full name too short: {name}");
345 }
346
347 #[test]
348 fn full_names_deterministic() {
349 let node = FullNames::new();
350 let mut out1 = [Value::None];
351 let mut out2 = [Value::None];
352 let h = xxh3_64(&99u64.to_le_bytes());
353 node.eval(&[Value::U64(h)], &mut out1);
354 node.eval(&[Value::U64(h)], &mut out2);
355 assert_eq!(out1[0].as_str(), out2[0].as_str());
356 }
357}