forked from TheAlgorithms/Python
-
Notifications
You must be signed in to change notification settings - Fork 0
/
convert_number_to_words.py
205 lines (181 loc) · 5.6 KB
/
convert_number_to_words.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
from enum import Enum
from typing import ClassVar, Literal
class NumberingSystem(Enum):
SHORT = (
(15, "quadrillion"),
(12, "trillion"),
(9, "billion"),
(6, "million"),
(3, "thousand"),
(2, "hundred"),
)
LONG = (
(15, "billiard"),
(9, "milliard"),
(6, "million"),
(3, "thousand"),
(2, "hundred"),
)
INDIAN = (
(14, "crore crore"),
(12, "lakh crore"),
(7, "crore"),
(5, "lakh"),
(3, "thousand"),
(2, "hundred"),
)
@classmethod
def max_value(cls, system: str) -> int:
"""
Gets the max value supported by the given number system.
>>> NumberingSystem.max_value("short") == 10**18 - 1
True
>>> NumberingSystem.max_value("long") == 10**21 - 1
True
>>> NumberingSystem.max_value("indian") == 10**19 - 1
True
"""
match (system_enum := cls[system.upper()]):
case cls.SHORT:
max_exp = system_enum.value[0][0] + 3
case cls.LONG:
max_exp = system_enum.value[0][0] + 6
case cls.INDIAN:
max_exp = 19
case _:
raise ValueError("Invalid numbering system")
return 10**max_exp - 1
class NumberWords(Enum):
ONES: ClassVar[dict[int, str]] = {
0: "",
1: "one",
2: "two",
3: "three",
4: "four",
5: "five",
6: "six",
7: "seven",
8: "eight",
9: "nine",
}
TEENS: ClassVar[dict[int, str]] = {
0: "ten",
1: "eleven",
2: "twelve",
3: "thirteen",
4: "fourteen",
5: "fifteen",
6: "sixteen",
7: "seventeen",
8: "eighteen",
9: "nineteen",
}
TENS: ClassVar[dict[int, str]] = {
2: "twenty",
3: "thirty",
4: "forty",
5: "fifty",
6: "sixty",
7: "seventy",
8: "eighty",
9: "ninety",
}
def convert_small_number(num: int) -> str:
"""
Converts small, non-negative integers with irregular constructions in English (i.e.,
numbers under 100) into words.
>>> convert_small_number(0)
'zero'
>>> convert_small_number(5)
'five'
>>> convert_small_number(10)
'ten'
>>> convert_small_number(15)
'fifteen'
>>> convert_small_number(20)
'twenty'
>>> convert_small_number(25)
'twenty-five'
>>> convert_small_number(-1)
Traceback (most recent call last):
...
ValueError: This function only accepts non-negative integers
>>> convert_small_number(123)
Traceback (most recent call last):
...
ValueError: This function only converts numbers less than 100
"""
if num < 0:
raise ValueError("This function only accepts non-negative integers")
if num >= 100:
raise ValueError("This function only converts numbers less than 100")
tens, ones = divmod(num, 10)
if tens == 0:
return NumberWords.ONES.value[ones] or "zero"
if tens == 1:
return NumberWords.TEENS.value[ones]
return (
NumberWords.TENS.value[tens]
+ ("-" if NumberWords.ONES.value[ones] else "")
+ NumberWords.ONES.value[ones]
)
def convert_number(
num: int, system: Literal["short", "long", "indian"] = "short"
) -> str:
"""
Converts an integer to English words.
:param num: The integer to be converted
:param system: The numbering system (short, long, or Indian)
>>> convert_number(0)
'zero'
>>> convert_number(1)
'one'
>>> convert_number(100)
'one hundred'
>>> convert_number(-100)
'negative one hundred'
>>> convert_number(123_456_789_012_345) # doctest: +NORMALIZE_WHITESPACE
'one hundred twenty-three trillion four hundred fifty-six billion
seven hundred eighty-nine million twelve thousand three hundred forty-five'
>>> convert_number(123_456_789_012_345, "long") # doctest: +NORMALIZE_WHITESPACE
'one hundred twenty-three thousand four hundred fifty-six milliard
seven hundred eighty-nine million twelve thousand three hundred forty-five'
>>> convert_number(12_34_56_78_90_12_345, "indian") # doctest: +NORMALIZE_WHITESPACE
'one crore crore twenty-three lakh crore
forty-five thousand six hundred seventy-eight crore
ninety lakh twelve thousand three hundred forty-five'
>>> convert_number(10**18)
Traceback (most recent call last):
...
ValueError: Input number is too large
>>> convert_number(10**21, "long")
Traceback (most recent call last):
...
ValueError: Input number is too large
>>> convert_number(10**19, "indian")
Traceback (most recent call last):
...
ValueError: Input number is too large
"""
word_groups = []
if num < 0:
word_groups.append("negative")
num *= -1
if num > NumberingSystem.max_value(system):
raise ValueError("Input number is too large")
for power, unit in NumberingSystem[system.upper()].value:
digit_group, num = divmod(num, 10**power)
if digit_group > 0:
word_group = (
convert_number(digit_group, system)
if digit_group >= 100
else convert_small_number(digit_group)
)
word_groups.append(f"{word_group} {unit}")
if num > 0 or not word_groups: # word_groups is only empty if input num was 0
word_groups.append(convert_small_number(num))
return " ".join(word_groups)
if __name__ == "__main__":
import doctest
doctest.testmod()
print(f"{convert_number(123456789) = }")