Skip to main content

language_distribution/
random_language.rs

1crate::ix!();
2
3pub trait RandomLanguage {
4    fn random_language(&self) -> Option<Language>;
5}
6
7impl RandomLanguage for Country {
8    fn random_language(&self) -> Option<Language> {
9        let dist = self.language_distribution();
10        if dist.is_empty() {
11            return None;
12        }
13
14        let langs: Vec<Language> = dist.keys().cloned().collect();
15        let weights: Vec<f64> = langs.iter().map(|l| dist[l]).collect();
16
17        // Create a weighted distribution
18        let weighted_dist = WeightedIndex::new(&weights).ok()?;
19        let mut rng = thread_rng();
20
21        // Sample a language based on the weighted distribution
22        Some(langs[weighted_dist.sample(&mut rng)].clone())
23    }
24}
25
26#[cfg(test)]
27mod random_language_tests {
28    use super::*;
29
30    #[test]
31    fn test_random_language_exists() {
32        let c = Country::Canada; // English (0.75), French (0.25)
33        let dist = c.language_distribution();
34        assert!(dist.contains_key(&Language::English));
35        assert!(dist.contains_key(&Language::French));
36
37        // Try random language multiple times to ensure we get a valid language
38        for _ in 0..10 {
39            let lang = c.random_language();
40            assert!(lang.is_some(), "Random language should not be None for Canada");
41            let chosen = lang.unwrap();
42            assert!(chosen == Language::English || chosen == Language::French, "Canada should yield English or French only");
43        }
44    }
45
46    #[test]
47    fn test_country_with_no_languages() {
48        // Hypothetical scenario: If a country not defined or a scenario leads to no languages
49        // We'll pick a country we defined everything for, no such scenario in real code.
50        // For the sake of testing, let's modify the approach:
51
52        struct EmptyCountry;
53        // We'll implement LanguageDistribution for EmptyCountry just for test
54        impl LanguageDistribution for EmptyCountry {
55            fn language_distribution(&self) -> HashMap<Language, f64> {
56                HashMap::new()
57            }
58        }
59
60        impl RandomLanguage for EmptyCountry {
61            fn random_language(&self) -> Option<Language> {
62                let dist = self.language_distribution();
63                if dist.is_empty() {
64                    return None;
65                }
66                None
67            }
68        }
69
70        let e = EmptyCountry;
71        assert!(e.language_distribution().is_empty());
72        assert!(e.random_language().is_none(), "No languages means no random language should be returned");
73    }
74
75    #[test]
76    fn test_random_language_distribution_proportions() {
77        // Test that random_language roughly follows distribution if tested statistically
78        // This is a probabilistic test. We'll do a rudimentary check by sampling.
79        // NOTE: This test is not guaranteed to pass every time due to randomness,
80        // but it gives a rough check. In real code, consider allowing some margin of error or mocking RNG.
81
82        let c = Country::Belgium; // Dutch (0.55), French (0.44), German (0.01)
83        let samples = 10_000;
84        let mut counts = HashMap::new();
85        counts.insert(Language::Dutch, 0);
86        counts.insert(Language::French, 0);
87        counts.insert(Language::German, 0);
88
89        for _ in 0..samples {
90            let lang = c.random_language().unwrap();
91            *counts.get_mut(&lang).unwrap() += 1;
92        }
93
94        let dutch_ratio = counts[&Language::Dutch] as f64 / samples as f64;
95        let french_ratio = counts[&Language::French] as f64 / samples as f64;
96        let german_ratio = counts[&Language::German] as f64 / samples as f64;
97
98        // We expect dutch_ratio ~ 0.55, french_ratio ~ 0.44, german_ratio ~0.01
99        // We'll allow a margin of error due to randomness:
100        assert!((dutch_ratio - 0.55).abs() < 0.05, "Dutch ratio should be close to 0.55");
101        assert!((french_ratio - 0.44).abs() < 0.05, "French ratio should be close to 0.44");
102        assert!((german_ratio - 0.01).abs() < 0.01, "German ratio should be close to 0.01");
103    }
104}
105