Skip to content

Commit 5ecb6ba

Browse files
tianyizheng02github-actions
and
github-actions
authored
Move and reimplement convert_number_to_words.py (TheAlgorithms#8998)
* Move and reimplement convert_number_to_words.py - Move convert_number_to_words.py from web_programming/ to conversions/ - Reimplement the algorithm from scratch because the logic was very opaque and too heavily nested - Add support for the Western numbering system (both short and long) because the original implementation only supported the Indian numbering system - Add extensive doctests and error handling * updating DIRECTORY.md --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
1 parent e887c14 commit 5ecb6ba

File tree

3 files changed

+206
-110
lines changed

3 files changed

+206
-110
lines changed

DIRECTORY.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
* [Binary To Decimal](conversions/binary_to_decimal.py)
144144
* [Binary To Hexadecimal](conversions/binary_to_hexadecimal.py)
145145
* [Binary To Octal](conversions/binary_to_octal.py)
146+
* [Convert Number To Words](conversions/convert_number_to_words.py)
146147
* [Decimal To Any](conversions/decimal_to_any.py)
147148
* [Decimal To Binary](conversions/decimal_to_binary.py)
148149
* [Decimal To Binary Recursion](conversions/decimal_to_binary_recursion.py)
@@ -1203,7 +1204,6 @@
12031204

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

web_programming/convert_number_to_words.py

-109
This file was deleted.

0 commit comments

Comments
 (0)