Skip to content

Commit d799026

Browse files
committed
Add Hindi language support for number to word conversion and related tests
1 parent 8202ba0 commit d799026

File tree

9 files changed

+379
-41
lines changed

9 files changed

+379
-41
lines changed

Logs/logs.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ def __handler_config(self):
4747
'file': {
4848
'class': 'logging.FileHandler',
4949
'filename': self.__filename,
50-
'formatter': 'default'
50+
'formatter': 'default',
51+
'encoding': 'utf-8'
5152
}
5253
} if self.__stream == ['console', 'file'] else {
5354
'file': {
5455
'class': 'logging.FileHandler',
5556
'filename': self.__filename,
56-
'formatter': 'default'
57+
'formatter': 'default',
58+
'encoding': 'utf-8'
5759
}
5860
} if self.__stream == ['file'] else {
5961
'console': {

NumWord/number_to_word.py

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,37 @@
22

33

44
class NumberToWord:
5-
def __init__(self, lang="en"):
6-
self.units = GetLanguage().get_language(lang)[1]["UNIT"]
7-
self.teens = GetLanguage().get_language(lang)[1]["TEENS"]
8-
self.tens = GetLanguage().get_language(lang)[1]["TENS"]
9-
self.thousands = GetLanguage().get_language(lang)[1]["THOUSANDS"]
10-
11-
def convert_hundreds(self, number):
5+
def __init__(self):
6+
self.symbol = None
7+
self.thousands = None
8+
self.tens = None
9+
self.others = None
10+
self.teens = None
11+
self.units = None
12+
13+
def convert_hundreds(self, number, lang):
1214
"""
1315
Convert a number in the hundreds range to its word representation.
1416
1517
Args:
1618
number (int): The number to convert.
19+
lang (str): The language to use for the conversion
1720
1821
Returns:
1922
str: The word representation of the number.
2023
"""
2124
if number > 99:
22-
return self.units[number // 100] + " hundred " + self.convert_tens(number % 100)
25+
return self.units[number // 100] + f" {self.zero[1]} " + self.convert_tens(number % 100, lang)
2326
else:
24-
return self.convert_tens(number)
27+
return self.convert_tens(number, lang)
2528

26-
def convert_tens(self, number):
29+
def convert_tens(self, number, lang):
2730
"""
2831
Convert a number in the tens range to its word representation.
2932
3033
Args:
3134
number (int): The number to convert.
35+
lang (str): The language to use for the conversion
3236
3337
Returns:
3438
str: The word representation of the number.
@@ -37,30 +41,34 @@ def convert_tens(self, number):
3741
return self.units[number]
3842
elif 10 < number < 20:
3943
return self.teens[number - 10]
44+
elif lang in ["hi"] and 20 <= number < 100:
45+
return self.others[number - 10]
4046
else:
4147
if number % 10 == 0:
4248
return self.tens[number // 10]
4349
return self.tens[number // 10] + " " + self.units[number % 10]
4450

45-
def convert_thousands(self, number):
51+
def convert_thousands(self, number, lang):
4652
"""
4753
Convert a number in the thousands range to its word representation.
4854
4955
Args:
5056
number (int): The number to convert.
57+
lang (str): The language to use for the conversion
5158
5259
Returns:
5360
str: The word representation of the number.
5461
"""
5562
if number == 0:
5663
return ""
5764
elif number < 1000:
58-
return self.convert_hundreds(number).strip()
65+
return self.convert_hundreds(number, lang).strip()
5966
else:
6067
for idx, word in enumerate(self.thousands):
61-
if number < 1000 ** (idx + 1):
62-
return self.convert_thousands(number // (1000 ** idx)) + " " + self.thousands[
63-
idx] + " " + self.convert_thousands(number % (1000 ** idx))
68+
divisor = 10 ** self.power[idx]
69+
if number >= divisor:
70+
return (self.convert_thousands(number // divisor, lang) + f" {self.thousands[idx]} " +
71+
self.convert_thousands(number % divisor, lang).strip())
6472

6573
def convert_decimal(self, number):
6674
"""
@@ -74,25 +82,43 @@ def convert_decimal(self, number):
7482
"""
7583
decimal_part = str(number).split(".")[1]
7684
decimal_words = " ".join(self.units[int(digit)] for digit in decimal_part)
77-
return "point " + decimal_words
85+
return f"{self.symbol[0]} " + decimal_words
7886

79-
def convert(self, number):
87+
def convert(self, number, lang="en"):
8088
"""
8189
Convert a number to its word representation.
8290
8391
Args:
8492
number (int or float): The number to convert.
93+
lang (str): The language to use for the conversion
8594
8695
Returns:
8796
str: The word representation of the number.
8897
"""
98+
self.__get_words(lang)
8999
if number == 0:
90-
return "zero"
100+
return self.zero[0]
91101
elif number < 0:
92-
return "negative " + self.convert(-number)
102+
return f"{self.symbol[1]} " + self.convert(-number, lang)
93103
else:
94104
if "." in str(number):
95105
integer_part = int(str(number).split(".")[0])
96-
return self.convert_thousands(integer_part).strip() + " " + self.convert_decimal(number)
106+
return self.convert_thousands(integer_part, lang).strip() + " " + self.convert_decimal(number)
97107
else:
98-
return self.convert_thousands(number).strip()
108+
return self.convert_thousands(number, lang).strip()
109+
110+
def __get_words(self, lang):
111+
"""
112+
Get the words for the number conversion based on the language.
113+
114+
Args:
115+
lang (str): The language to use for the conversion
116+
"""
117+
self.zero = GetLanguage().get_language(lang)[1]["ZERO"]
118+
self.units = GetLanguage().get_language(lang)[1]["UNIT"]
119+
self.teens = GetLanguage().get_language(lang)[1]["TEENS"]
120+
self.others = GetLanguage().get_language(lang)[1]["OTHERS"]
121+
self.tens = GetLanguage().get_language(lang)[1]["TENS"]
122+
self.thousands = GetLanguage().get_language(lang)[1]["THOUSANDS"][::-1]
123+
self.symbol = GetLanguage().get_language(lang)[2]
124+
self.power = GetLanguage().get_language(lang)[1]["POWER"][::-1]

NumWord/word_to_number.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
class WordToNumber:
55
def __init__(self, lang="en"):
66
self.word_to_num = GetLanguage().get_language(lang)[0]
7+
self.symbol = GetLanguage().get_language(lang)[2]
78

89
def convert(self, words):
910
"""
@@ -20,9 +21,9 @@ def convert(self, words):
2021
is_decimal, is_negative = False, False
2122

2223
for word in words:
23-
if word == "negative":
24+
if word == self.symbol[1]:
2425
is_negative = True
25-
elif word == "point":
26+
elif word == self.symbol[0]:
2627
is_decimal = True
2728
elif word.isdigit():
2829
current, decimal_part, decimal_place = self.process_digit(word, is_decimal, current, decimal_part,

tests/test_english.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class TestNumToWord(unittest.TestCase):
8787
def setUpClass(cls):
8888
cls.logger = LoggerConfig(__name__).get_logger()
8989
cls.logger.info("TestNumToWord started.")
90-
cls.num_to_word = NumberToWord(lang='en')
90+
cls.num_to_word = NumberToWord()
9191

9292
@classmethod
9393
def tearDownClass(cls):
@@ -166,8 +166,9 @@ def test_hundred_then_thousand(self):
166166
self.assertEqual(result, "one hundred five thousand")
167167

168168
def test_more_then_nonillion(self):
169-
with self.assertRaises(AttributeError):
170-
self.num_to_word.convert(1000000000000000000000000000000000)
169+
result = self.num_to_word.convert(10 ** 30)
170+
self.logger.info(f"Test more than nonillion: {10 ** 30} -> {result}")
171+
self.assertEqual(result, "one nonillion")
171172

172173
if __name__ == '__main__':
173174
unittest.main()

tests/test_hindi.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import unittest
2+
3+
from Logs import LoggerConfig
4+
from NumWord import WordToNumber, NumberToWord
5+
6+
7+
class TestWordToNum(unittest.TestCase):
8+
9+
@classmethod
10+
def setUpClass(cls):
11+
cls.logger = LoggerConfig(__name__).get_logger()
12+
cls.logger.info("TestWordToNum started.")
13+
cls.word_to_num = WordToNumber(lang='hi')
14+
15+
@classmethod
16+
def tearDownClass(cls):
17+
cls.logger.info("TestWordToNum completed. \n -----------------")
18+
19+
def test_single_digit(self):
20+
result = self.word_to_num.convert("छह")
21+
self.logger.info(f"Test single digit: 'छह' -> {result}")
22+
self.assertEqual(result, 6)
23+
24+
def test_two_digits(self):
25+
result = self.word_to_num.convert("इक्कीस")
26+
self.logger.info(f"Test two digits: 'इक्कीस' -> {result}")
27+
self.assertEqual(result, 21)
28+
29+
def test_negative_number(self):
30+
result = self.word_to_num.convert("एक हजार दो सौ चौंतीस")
31+
self.logger.info(f"Test negative number: 'एक हजार दो सौ चौंतीस' -> {result}")
32+
self.assertEqual(result, 1234)
33+
34+
def test_large_number(self):
35+
result = self.word_to_num.convert("एक हजार दो सौ चौंतीस")
36+
self.logger.info(f"Test large number: 'एक हजार दो सौ चौंतीस' -> {result}")
37+
self.assertEqual(result, 1234)
38+
39+
def test_decimal_number(self):
40+
result = self.word_to_num.convert("एक दशमलव पांच")
41+
self.logger.info(f"Test decimal number: 'एक दशमलव पांच' -> {result}")
42+
self.assertEqual(result, 1.5)
43+
44+
def test_mixed_number(self):
45+
result = self.word_to_num.convert("एक सौ तेईस दशमलव चार पांच छह")
46+
self.logger.info(f"Test mixed number: 'एक सौ तेईस दशमलव चार पांच छह' -> {result}")
47+
self.assertEqual(result, 123.456)
48+
49+
def test_million_number(self):
50+
result = self.word_to_num.convert("दस लाख")
51+
self.logger.info(f"Test million number: 'दस लाख' -> {result}")
52+
self.assertEqual(result, 1000000)
53+
54+
def test_invalid_word(self):
55+
with self.assertRaises(ValueError):
56+
self.word_to_num.convert("अमान्य")
57+
58+
def test_shank(self):
59+
result = self.word_to_num.convert("एक शंख")
60+
self.logger.info(f"Test shank: 'एक शंख' -> {result}")
61+
self.assertEqual(result, 100000000000000000)
62+
63+
def test_kharab(self):
64+
result = self.word_to_num.convert("एक खरब")
65+
self.logger.info(f"Test kharab: 'एक खरब' -> {result}")
66+
self.assertEqual(result, 100000000000)
67+
68+
69+
class TestNumToWord(unittest.TestCase):
70+
71+
@classmethod
72+
def setUpClass(cls):
73+
cls.logger = LoggerConfig(__name__).get_logger()
74+
cls.logger.info("TestNumToWord started.")
75+
cls.num_to_word = NumberToWord()
76+
77+
@classmethod
78+
def tearDownClass(cls):
79+
cls.logger.info("TestNumToWord completed. \n -----------------")
80+
81+
def test_single_digit(self):
82+
result = self.num_to_word.convert(6, lang='hi')
83+
self.logger.info(f"Test single digit: 6 -> {result}")
84+
self.assertEqual(result, "छह")
85+
86+
def test_two_digits(self):
87+
result = self.num_to_word.convert(21, lang='hi')
88+
self.logger.info(f"Test two digits: 21 -> {result}")
89+
self.assertEqual(result, "इक्कीस")
90+
91+
def test_negative_number(self):
92+
result = self.num_to_word.convert(-1234, lang='hi')
93+
self.logger.info(f"Test negative number: -1234 -> {result}")
94+
self.assertEqual(result, "ऋणात्मक एक हजार दो सौ चौंतीस")
95+
96+
def test_large_number(self):
97+
result = self.num_to_word.convert(1234, lang='hi')
98+
self.logger.info(f"Test large number: 1234 -> {result}")
99+
self.assertEqual(result, "एक हजार दो सौ चौंतीस")
100+
101+
def test_decimal_number(self):
102+
result = self.num_to_word.convert(1.5, lang='hi')
103+
self.logger.info(f"Test decimal number: 1.5 -> {result}")
104+
self.assertEqual(result, "एक दशमलव पांच")
105+
106+
def test_mixed_number(self):
107+
result = self.num_to_word.convert(123.456, lang='hi')
108+
self.logger.info(f"Test mixed number: 123.456 -> {result}")
109+
self.assertEqual(result, "एक सौ तेईस दशमलव चार पांच छह")
110+
111+
def test_million_number(self):
112+
result = self.num_to_word.convert(1000000, lang='hi')
113+
self.logger.info(f"Test million number: 1000000 -> {result}")
114+
self.assertEqual(result, "दस लाख")
115+
116+
117+
if __name__ == '__main__':
118+
unittest.main()

utility/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
from .english import ENGLISH_NUM_WORDS, ENGLISH_WORD_NUM
2+
from .hindi import HINDI_NUM_WORDS, HINDI_WORD_NUM
3+
4+
from .symboles import SYMBOLS
25

36

47
class GetLanguage:
58
def get_language(self, language):
69
if language == "en":
7-
return ENGLISH_WORD_NUM, ENGLISH_NUM_WORDS
10+
return ENGLISH_WORD_NUM, ENGLISH_NUM_WORDS, SYMBOLS["en"]
11+
elif language == "hi":
12+
return HINDI_WORD_NUM, HINDI_NUM_WORDS, SYMBOLS["hi"]
813
else:
914
raise NotImplementedError(f"Language {language} is not supported.")

utility/english.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,35 @@
2828
"eighty": 80,
2929
"ninety": 90,
3030
"hundred": 100,
31-
"thousand": 1000,
32-
"million": 1000000,
33-
"billion": 1000000000,
34-
"trillion": 1000000000000,
35-
"quadrillion": 1000000000000000,
36-
"quintillion": 1000000000000000000,
37-
"sextillion": 1000000000000000000000,
38-
"septillion": 1000000000000000000000000,
39-
"octillion": 1000000000000000000000000000,
40-
"nonillion": 1000000000000000000000000000000,
31+
"thousand": 10 ** 3,
32+
"million": 10 ** 6,
33+
"billion": 10 ** 9,
34+
"trillion": 10 ** 12,
35+
"quadrillion": 10 ** 15,
36+
"quintillion": 10 ** 18,
37+
"sextillion": 10 ** 21,
38+
"septillion": 10 ** 24,
39+
"octillion": 10 ** 27,
40+
"nonillion": 10 ** 30,
41+
"decillion": 10 ** 33,
42+
"undecillion": 10 ** 36,
43+
"duodecillion": 10 ** 39,
44+
# "tredecillion": 10 ** 42,
45+
# "quattuordecillion": 10 ** 45,
46+
# "quindecillion": 10 ** 48,
47+
# "sexdecillion": 10 ** 51,
48+
# "septendecillion": 10 ** 54,
49+
# "octodecillion": 10 ** 57,
4150
}
4251

4352
ENGLISH_NUM_WORDS = {
53+
"ZERO": ["zero", "hundred"],
4454
"UNIT": ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"],
4555
"TEENS": ["", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
4656
"nineteen"],
57+
"OTHERS": [""],
4758
"TENS": ["", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"],
4859
"THOUSANDS": ["", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion",
49-
"septillion", "octillion", "nonillion"]
60+
"septillion", "octillion", "nonillion", "decillion", "undecillion", "duodecillion"],
61+
"POWER": [1, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39]
5062
}
51-

0 commit comments

Comments
 (0)