jabba_lib/
jspell.rs

1//! spell
2
3use once_cell::sync::Lazy;
4use std::collections::HashMap;
5
6use crate::jmath;
7
8/*********
9  public
10**********/
11
12/// Returns the spelled (written out in words) version of the given number.
13///
14/// Current limitation: the number must be between 0 and 1000 (inclusive).
15///
16/// # Examples
17///
18/// ```
19/// assert_eq!(jabba_lib::jspell::spell_number(0), "zero");
20/// assert_eq!(jabba_lib::jspell::spell_number(115), "one hundred and fifteen");
21/// assert_eq!(jabba_lib::jspell::spell_number(342), "three hundred and forty-two");
22/// ```
23pub fn spell_number(n: u32) -> String {
24    assert!(n <= 1000);
25
26    let digits = jmath::digits(n as u64);
27    match digits.len() {
28        1 => length_1(&digits),
29        2 => length_2(&digits),
30        3 => length_3(&digits),
31        4 => length_4(&digits),
32        _ => String::from("unknown"),
33    }
34}
35
36/**********
37  private
38***********/
39
40static NUMBERS: Lazy<HashMap<i32, &str>> = Lazy::new(|| {
41    HashMap::from([
42        (0, "zero"),
43        (1, "one"),
44        (2, "two"),
45        (3, "three"),
46        (4, "four"),
47        (5, "five"),
48        (6, "six"),
49        (7, "seven"),
50        (8, "eight"),
51        (9, "nine"),
52        (10, "ten"),
53        (11, "eleven"),
54        (12, "twelve"),
55        (13, "thirteen"),
56        (14, "fourteen"),
57        (15, "fifteen"),
58        (16, "sixteen"),
59        (17, "seventeen"),
60        (18, "eighteen"),
61        (19, "nineteen"),
62        (20, "twenty"),
63        (30, "thirty"),
64        (40, "forty"),
65        (50, "fifty"),
66        (60, "sixty"),
67        (70, "seventy"),
68        (80, "eighty"),
69        (90, "ninety"),
70        (100, "one hundred"),
71        (1000, "one thousand"),
72    ])
73});
74
75fn length_1(digits: &[i32]) -> String {
76    NUMBERS.get(&digits[0]).unwrap().to_string()
77}
78
79fn length_2(digits: &[i32]) -> String {
80    let n = digits[0] * 10 + digits[1];
81    if digits[0] == 1 {
82        NUMBERS.get(&n).unwrap().to_string()
83    } else {
84        if digits[1] == 0 {
85            return NUMBERS.get(&n).unwrap().to_string();
86        }
87        // else
88        let n = digits[0] * 10;
89        format!(
90            "{}-{}",
91            NUMBERS.get(&n).unwrap(),
92            NUMBERS.get(&digits[1]).unwrap()
93        )
94    }
95}
96
97fn length_3(digits: &[i32]) -> String {
98    let head = digits[0];
99    let tail = digits[1] * 10 + digits[2];
100    if tail == 0 {
101        // "00"
102        format!("{} hundred", length_1(&[head]))
103    } else if tail < 10 {
104        // "01", "02", ..., "09"
105        format!("{} hundred and {}", length_1(&[head]), length_1(&[tail]))
106    } else {
107        let tail_digits = jmath::digits(tail as u64);
108        format!(
109            "{} hundred and {}",
110            length_1(&[head]),
111            length_2(&tail_digits)
112        )
113    }
114}
115
116fn length_4(digits: &[i32]) -> String {
117    let n = digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3];
118    NUMBERS.get(&n).unwrap().to_string()
119}
120
121// ==========================================================================
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn spell_number_test1() {
129        assert_eq!(spell_number(0), "zero");
130        assert_eq!(spell_number(0), "zero");
131        assert_eq!(spell_number(1), "one");
132        assert_eq!(spell_number(2), "two");
133        assert_eq!(spell_number(3), "three");
134        assert_eq!(spell_number(4), "four");
135        assert_eq!(spell_number(5), "five");
136        assert_eq!(spell_number(6), "six");
137        assert_eq!(spell_number(7), "seven");
138        assert_eq!(spell_number(8), "eight");
139        assert_eq!(spell_number(9), "nine");
140        assert_eq!(spell_number(10), "ten");
141        assert_eq!(spell_number(11), "eleven");
142        assert_eq!(spell_number(12), "twelve");
143        assert_eq!(spell_number(13), "thirteen");
144        assert_eq!(spell_number(14), "fourteen");
145        assert_eq!(spell_number(15), "fifteen");
146        assert_eq!(spell_number(16), "sixteen");
147        assert_eq!(spell_number(17), "seventeen");
148        assert_eq!(spell_number(18), "eighteen");
149        assert_eq!(spell_number(19), "nineteen");
150        assert_eq!(spell_number(20), "twenty");
151        assert_eq!(spell_number(30), "thirty");
152        assert_eq!(spell_number(40), "forty");
153        assert_eq!(spell_number(50), "fifty");
154        assert_eq!(spell_number(60), "sixty");
155        assert_eq!(spell_number(70), "seventy");
156        assert_eq!(spell_number(80), "eighty");
157        assert_eq!(spell_number(90), "ninety");
158        assert_eq!(spell_number(100), "one hundred");
159        assert_eq!(spell_number(1000), "one thousand");
160        //
161        assert_eq!(spell_number(115), "one hundred and fifteen");
162        assert_eq!(spell_number(342), "three hundred and forty-two");
163    }
164
165    #[test]
166    fn spell_number_test2() {
167        let mut sb = String::new();
168        for n in 1..1000 + 1 {
169            sb.push_str(&spell_number(n));
170        }
171        sb = sb.replace(" ", "").replace("-", "");
172        assert_eq!(sb.len(), 21124);
173    }
174}