random_zh/
random_zh.rs

1use crate::data::Data;
2use rand::{Rng, rng, seq::SliceRandom};
3
4/// Options for the `random_zh` function.
5///
6/// # Fields
7/// - `count` (Optional): The number of random characters to generate. Defaults to 1 if not specified.
8/// - `level_range` (Optional): A range of levels (e.g., `[1, 3]`) to filter the characters.
9/// - `stroke_count_range` (Optional): A range of stroke counts (e.g., `[5, 10]`) to filter the characters.
10/// - `allow_duplicates` (Default: `false`): Whether duplicate characters are allowed in the result.
11pub struct RandomZhOptions {
12    pub count: Option<usize>,
13    pub level_range: Option<(u8, u8)>,
14    pub stroke_count_range: Option<(u8, u8)>,
15    pub allow_duplicates: bool,
16}
17
18impl Default for RandomZhOptions {
19    fn default() -> Self {
20        Self {
21            count: None,
22            level_range: None,
23            stroke_count_range: None,
24            allow_duplicates: false,
25        }
26    }
27}
28
29/// Generates random Chinese characters based on the specified options.
30///
31/// # Arguments
32/// - `options` (RandomZhOptions): Configuration for generating random characters.
33///
34/// # Returns
35/// A `Vec<char>` containing the randomly generated characters.
36///
37/// # Examples
38/// ```
39/// use random_zh::{random_zh, RandomZhOptions};
40///
41/// // Generate 1 random characters.
42/// let chars = random_zh(RandomZhOptions {
43///     ..Default::default()
44/// });
45/// println!("{:?}", chars);
46///
47/// // Generate 5 random characters.
48/// let chars = random_zh(RandomZhOptions {
49///     count: Some(5),
50///     ..Default::default()
51/// });
52/// println!("{:?}", chars);
53///
54/// // Generate characters with a specific level range.
55/// let chars = random_zh(RandomZhOptions {
56///     level_range: Some((1, 1)), // (1, 2), (2, 3), etc.
57///     ..Default::default()
58/// });
59/// println!("{:?}", chars);
60///
61/// // Generate characters with a specific stroke count range.
62/// let chars = random_zh(RandomZhOptions {
63///    stroke_count_range: Some((1, 36)), // (1, 6), (10, 10), etc.
64///    ..Default::default()
65/// });
66///
67/// // Generate characters with a stroke count range and allow duplicates.
68/// let chars = random_zh(RandomZhOptions {
69///     count: Some(50),
70///     level_range: Some((1, 3)),
71///     stroke_count_range: Some((1, 36)),
72///     allow_duplicates: true,
73/// });
74/// println!("{:?}", chars);
75/// ```
76pub fn random_zh(options: RandomZhOptions) -> Vec<char> {
77    // Load data
78    let data = Data::new();
79
80    // Filter characters by level range
81    let mut candidates: Vec<char> = if let Some((min, max)) = options.level_range {
82        data.levels
83            .iter()
84            .filter(|&(level, _)| *level >= min && *level <= max)
85            .flat_map(|(_, chars)| chars.clone())
86            .collect()
87    } else {
88        data.levels
89            .values()
90            .flat_map(|chars| chars.clone())
91            .collect()
92    };
93
94    // Further filter by stroke count range
95    if let Some((min, max)) = options.stroke_count_range {
96        candidates = candidates
97            .into_iter()
98            .filter(|&c| {
99                data.stroke_counts
100                    .iter()
101                    .any(|(&strokes, chars)| strokes >= min && strokes <= max && chars.contains(&c))
102            })
103            .collect();
104    }
105
106    // Shuffle the candidates
107    let mut rng = rng();
108    candidates.shuffle(&mut rng);
109
110    let count = options.count.unwrap_or(1);
111
112    if options.allow_duplicates {
113        // Allow duplicates: Repeat characters as needed
114        let mut result = Vec::new();
115        for _ in 0..count {
116            if !candidates.is_empty() {
117                let index = rng.random_range(0..candidates.len());
118                result.push(candidates[index]);
119            }
120        }
121        result
122    } else {
123        // No duplicates: Take unique characters only
124        candidates.into_iter().take(count).collect()
125    }
126}