1#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(html_favicon_url = "https://nil.dev.br/favicon.png")]
6
7use itertools::Itertools;
8use rand::random_range;
9use rand::seq::IndexedRandom;
10use std::sync::Arc;
11use strum::VariantArray;
12
13const FEMALE: &str = include_str!("../data/female.csv");
14const MALE: &str = include_str!("../data/male.csv");
15const SURNAME: &str = include_str!("../data/surname.csv");
16
17pub fn generate(amount: usize) -> Vec<Arc<str>> {
18 let mut names = Vec::with_capacity(amount);
19 let mut female = lines(FEMALE);
20 let mut male = lines(MALE);
21 let mut surname = lines(SURNAME);
22
23 for _ in 0..=amount {
24 let gender = Gender::random();
25 let Some(first) = (match gender {
26 Gender::Female => take(&mut female),
27 Gender::Male => take(&mut male),
28 }) else {
29 break;
30 };
31
32 let Some(last) = take(&mut surname) else { break };
33 if first != last {
34 names.push(Arc::from(format!("{first} {last}")));
35 }
36 }
37
38 names.into_iter().unique().collect()
39}
40
41fn lines(file: &'static str) -> Vec<&'static str> {
42 file
43 .trim()
44 .lines()
45 .map(str::trim)
46 .filter(|line| !line.is_empty())
47 .unique()
48 .collect()
49}
50
51fn take(pool: &mut Vec<&'static str>) -> Option<&'static str> {
52 if pool.is_empty() {
53 None
54 } else {
55 let idx = random_range(0..pool.len());
56 Some(pool.swap_remove(idx))
57 }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, VariantArray)]
61enum Gender {
62 Female,
63 Male,
64}
65
66impl Gender {
67 fn random() -> Self {
68 Self::VARIANTS
69 .choose(&mut rand::rng())
70 .copied()
71 .expect("Self::VARIANTS should never be empty")
72 }
73}