From 3e390677946cd56f87851f44e92009d1ac5faeb4 Mon Sep 17 00:00:00 2001 From: Innocent Zenda Date: Sun, 24 Sep 2023 11:34:21 +0300 Subject: [PATCH 1/3] add num2words for swahili language --- num2words/__init__.py | 63 ++++++++++++++++++++++++++++----- num2words/lang_SW.py | 81 +++++++++++++++++++++++++++++++++++++++++++ tests/test_sw.py | 62 +++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 num2words/lang_SW.py create mode 100644 tests/test_sw.py diff --git a/num2words/__init__.py b/num2words/__init__.py index 53a28591..8d887f44 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -17,14 +17,58 @@ from __future__ import unicode_literals -from . import (lang_AM, lang_AR, lang_AZ, lang_CZ, lang_DE, lang_DK, lang_EN, - lang_EN_IN, lang_EN_NG, lang_EO, lang_ES, lang_ES_CO, - lang_ES_GT, lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, - lang_FR_BE, lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, - lang_IS, lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, - lang_LV, lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, - lang_RO, lang_RU, lang_SK, lang_SL, lang_SR, lang_SV, lang_TE, - lang_TG, lang_TH, lang_TR, lang_UK, lang_VI) +from . import ( + lang_AM, + lang_AR, + lang_AZ, + lang_CZ, + lang_DE, + lang_DK, + lang_EN, + lang_EN_IN, + lang_EN_NG, + lang_EO, + lang_ES, + lang_ES_CO, + lang_ES_GT, + lang_ES_NI, + lang_ES_VE, + lang_FA, + lang_FI, + lang_FR, + lang_FR_BE, + lang_FR_CH, + lang_FR_DZ, + lang_HE, + lang_HU, + lang_ID, + lang_IS, + lang_IT, + lang_JA, + lang_KN, + lang_KO, + lang_KZ, + lang_LT, + lang_LV, + lang_NL, + lang_NO, + lang_PL, + lang_PT, + lang_PT_BR, + lang_RO, + lang_RU, + lang_SK, + lang_SL, + lang_SR, + lang_SV, + lang_SW, + lang_TE, + lang_TG, + lang_TH, + lang_TR, + lang_UK, + lang_VI, +) CONVERTER_CLASSES = { 'am': lang_AM.Num2Word_AM(), @@ -75,7 +119,8 @@ 'uk': lang_UK.Num2Word_UK(), 'te': lang_TE.Num2Word_TE(), 'hu': lang_HU.Num2Word_HU(), - 'is': lang_IS.Num2Word_IS() + 'is': lang_IS.Num2Word_IS(), + 'sw': lang_SW.Num2Word_SW(), } CONVERTES_TYPES = ['cardinal', 'ordinal', 'ordinal_num', 'year', 'currency'] diff --git a/num2words/lang_SW.py b/num2words/lang_SW.py new file mode 100644 index 00000000..e223add5 --- /dev/null +++ b/num2words/lang_SW.py @@ -0,0 +1,81 @@ +from num2words.base import Num2Word_Base + +ONES = [ + 'sifuri', + 'moja', + 'mbili', + 'tatu', + 'nne', + 'tano', + 'sita', + 'saba', + 'nane', + 'tisa', +] + +TENS_STR = { + 1: 'kumi', + 2: 'ishirini', + 3: 'thelathini', + 4: 'arobaini', + 5: 'hamsini', + 6: 'sitini', + 7: 'sabini', + 8: 'themanini', + 9: 'tisini', +} + + +class Num2Word_SW(Num2Word_Base): + MINUS_PREFIX_WORD = 'hasi ' + + def tens_to_cardinal(self, number): + tens = number // 10 + units = number % 10 + + if tens in TENS_STR: + prefix = TENS_STR[tens] + + postfix = ONES[units] + + if units == 0: + return prefix + + return f'{prefix} na {postfix}' + + def hundreds_to_cardinal(self, number): + hundreds = number // 100 + remainder = number % 100 + + if remainder == 0: + return f'mia {ONES[hundreds]}' + elif remainder < 10: + return f'mia {ONES[hundreds]} na {ONES[remainder]}' + elif remainder < 100: + return f'mia {ONES[hundreds]} {self.tens_to_cardinal(remainder)}' + + def thousands_to_cardinal(self, number): + thousands = number // 1000 + remainder = number % 1000 + + if remainder == 0: + return f'elfu {ONES[thousands]}' + elif remainder < 10: + return f'{ONES[thousands]} elfu na {ONES[remainder]}' + elif remainder < 100: + return f'{ONES[thousands]} elfu na {self.tens_to_cardinal(remainder)}' + elif remainder < 1000: + return f'{ONES[thousands]} elfu, {self.hundreds_to_cardinal(remainder)}' + + def to_cardinal(self, number): + if number < 0: + string = self.MINUS_PREFIX_WORD + self.to_cardinal(-number) + elif number < 10: + string = ONES[number] + elif number < 100: + string = self.tens_to_cardinal(number) + elif number < 1000: + string = self.hundreds_to_cardinal(number) + elif number < 10_000: + string = self.thousands_to_cardinal(number) + return string diff --git a/tests/test_sw.py b/tests/test_sw.py new file mode 100644 index 00000000..0bb4ce73 --- /dev/null +++ b/tests/test_sw.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from __future__ import unicode_literals + +from unittest import TestCase + +from num2words import num2words + + +class Num2WordsSWTest(TestCase): + def test_0(self): + self.assertEqual(num2words(0, lang='sw'), 'sifuri') + + def test_1_to_9(self): + self.assertEqual(num2words(1, lang='sw'), 'moja') + self.assertEqual(num2words(2, lang='sw'), 'mbili') + self.assertEqual(num2words(9, lang='sw'), 'tisa') + + def test_tens_10_to_99(self): + self.assertEqual(num2words(10, lang='sw'), 'kumi') + self.assertEqual(num2words(11, lang='sw'), 'kumi na moja') + self.assertEqual(num2words(20, lang='sw'), 'ishirini') + self.assertEqual(num2words(27, lang='sw'), 'ishirini na saba') + self.assertEqual(num2words(90, lang='sw'), 'tisini') + self.assertEqual(num2words(99, lang='sw'), 'tisini na tisa') + + def test_hundreds_100_to_999(self): + self.assertEqual(num2words(100, lang='sw'), 'mia moja') + self.assertEqual(num2words(608, lang='sw'), 'mia sita na nane') + self.assertEqual(num2words(500, lang='sw'), 'mia tano') + self.assertEqual(num2words(572, lang='sw'), 'mia tano sabini na mbili') + self.assertEqual(num2words(700, lang='sw'), 'mia saba') + self.assertEqual(num2words(999, lang='sw'), 'mia tisa tisini na tisa') + + def test_thousands_1000_to_9999(self): + self.assertEqual(num2words(1000, lang='sw'), 'elfu moja') + self.assertEqual(num2words(7000, lang='sw'), 'elfu saba') + self.assertEqual(num2words(9000, lang='sw'), 'elfu tisa') + self.assertEqual( + num2words(1322, lang='sw'), 'moja elfu, mia tatu ishirini na mbili' + ) + self.assertEqual( + num2words(6987, lang='sw'), 'sita elfu, mia tisa themanini na saba' + ) + self.assertEqual( + num2words(9999, lang='sw'), 'tisa elfu, mia tisa tisini na tisa' + ) From edb3aa1745345f541899f86a1c0dc51f7a6fc71f Mon Sep 17 00:00:00 2001 From: Innocent Zenda Date: Sun, 24 Sep 2023 17:24:35 +0300 Subject: [PATCH 2/3] handle big numbers --- num2words/lang_SW.py | 131 ++++++++++++++++++++++++++++++++++++++----- tests/test_sw.py | 63 ++++++++++++++++++++- 2 files changed, 178 insertions(+), 16 deletions(-) diff --git a/num2words/lang_SW.py b/num2words/lang_SW.py index e223add5..e3e068ca 100644 --- a/num2words/lang_SW.py +++ b/num2words/lang_SW.py @@ -13,6 +13,19 @@ 'tisa', ] +ONES_STR = { + 0: 'sifuri', + 1: 'moja', + 2: 'mbili', + 3: 'tatu', + 4: 'nne', + 5: 'tano', + 6: 'sita', + 7: 'saba', + 8: 'nane', + 9: 'tisa', +} + TENS_STR = { 1: 'kumi', 2: 'ishirini', @@ -25,25 +38,38 @@ 9: 'tisini', } +EXPONENT_PREFIXES = { + 6: 'm', + 9: 'b', + 12: 'tr', + 15: 'quadr', + 18: 'quint', + 21: 'sext', + 24: 'sept', + 27: 'oct', + 30: 'non', + 33: 'dec', +} + class Num2Word_SW(Num2Word_Base): MINUS_PREFIX_WORD = 'hasi ' def tens_to_cardinal(self, number): + '''Converts a number less than 100 to a string.''' tens = number // 10 - units = number % 10 + remainder = number % 10 - if tens in TENS_STR: - prefix = TENS_STR[tens] + prefix = TENS_STR[tens] + postfix = ONES[remainder] - postfix = ONES[units] - - if units == 0: + if remainder == 0: return prefix return f'{prefix} na {postfix}' def hundreds_to_cardinal(self, number): + '''Converts a number less than 1000 to a string.''' hundreds = number // 100 remainder = number % 100 @@ -51,31 +77,106 @@ def hundreds_to_cardinal(self, number): return f'mia {ONES[hundreds]}' elif remainder < 10: return f'mia {ONES[hundreds]} na {ONES[remainder]}' - elif remainder < 100: + else: return f'mia {ONES[hundreds]} {self.tens_to_cardinal(remainder)}' def thousands_to_cardinal(self, number): + '''Converts a number less than 1_000_000 to a string.''' thousands = number // 1000 remainder = number % 1000 if remainder == 0: - return f'elfu {ONES[thousands]}' + if thousands < 10: + # thousands = 1 - 9 + return f'elfu {ONES[thousands]}' + elif thousands < 100: + # thousands = 10 - 99 + return f'{self.tens_to_cardinal(thousands)} elfu' + else: + # thousands = 100 - 999 + return f'{self.hundreds_to_cardinal(thousands)} elfu' elif remainder < 10: - return f'{ONES[thousands]} elfu na {ONES[remainder]}' + if thousands < 10: + # thousands = 1 - 9 + return f'{ONES[thousands]} elfu na {ONES[remainder]}' + elif thousands < 100: + # thousands = 10 - 99 + return f'{self.tens_to_cardinal(thousands)} elfu na {ONES[remainder]}' + elif thousands < 1000: + # thousands = 100 - 999 + return ( + f'{self.hundreds_to_cardinal(thousands)} elfu na {ONES[remainder]}' + ) elif remainder < 100: - return f'{ONES[thousands]} elfu na {self.tens_to_cardinal(remainder)}' + if thousands < 10: + # thousands = 1 - 9 + return f'{ONES[thousands]} elfu na {self.tens_to_cardinal(remainder)}' + elif thousands < 100: + # thousands = 10 - 99 + return f'{self.tens_to_cardinal(thousands)} elfu na {self.tens_to_cardinal(remainder)}' + elif thousands < 1000: + # thousands = 100 - 999 + return f'{self.hundreds_to_cardinal(thousands)} elfu, {self.tens_to_cardinal(remainder)}' elif remainder < 1000: - return f'{ONES[thousands]} elfu, {self.hundreds_to_cardinal(remainder)}' + if thousands < 10: + # thousands = 1 - 9 + return f'{ONES[thousands]} elfu, {self.hundreds_to_cardinal(remainder)}' + elif thousands < 100: + # thousands = 10 - 99 + return f'{self.tens_to_cardinal(thousands)} elfu, {self.hundreds_to_cardinal(remainder)}' + else: + # thousands = 100 - 999 + return f'{self.hundreds_to_cardinal(thousands)} elfu, {self.hundreds_to_cardinal(remainder)}' + + def big_number_to_cardinal(self, number): + digits = [c for c in str(number)] + length = len(digits) + if length >= 66: + raise NotImplementedError('The given number is too large.') + + predigits = length % 3 or 3 + num = int(''.join(digits[:predigits])) + if num < 10: + num_in_words = ONES_STR[num] + elif num < 100: + num_in_words = self.tens_to_cardinal(num) + else: + num_in_words = self.hundreds_to_cardinal(num) + + word = EXPONENT_PREFIXES[len(digits[predigits:])] + 'ilioni' + base = f'{num_in_words} {word}' + number = int(''.join(digits[predigits:])) # remaining number + + if number == 0: + return f'{word} {num_in_words}' + elif number < 10: + return f'{base} na {ONES[number]}' + elif number < 100: + return f'{base} na {self.tens_to_cardinal(number)}' + elif number < 1000: + return f'{base}, {self.hundreds_to_cardinal(number)}' + elif number < 1_000_000: + return f'{base}, {self.thousands_to_cardinal(number)}' + else: + return f'{base}, {self.big_number_to_cardinal(number)}' def to_cardinal(self, number): if number < 0: + # negative number string = self.MINUS_PREFIX_WORD + self.to_cardinal(-number) elif number < 10: - string = ONES[number] + # 1 - 9 + string = ONES[int(number)] elif number < 100: - string = self.tens_to_cardinal(number) + # 10 - 99 + string = self.tens_to_cardinal(int(number)) elif number < 1000: + # 100 - 999 string = self.hundreds_to_cardinal(number) - elif number < 10_000: - string = self.thousands_to_cardinal(number) + elif number < 1_000_000: + # 100 - 999999 + string = self.thousands_to_cardinal(int(number)) + else: + # 1_000_000 - Infinity + string = self.big_number_to_cardinal(int(number)) return string diff --git a/tests/test_sw.py b/tests/test_sw.py index 0bb4ce73..1d22c34b 100644 --- a/tests/test_sw.py +++ b/tests/test_sw.py @@ -23,12 +23,21 @@ class Num2WordsSWTest(TestCase): + def test_negative(self): + self.assertEqual(num2words(-1, lang='sw'), 'hasi moja') + def test_0(self): self.assertEqual(num2words(0, lang='sw'), 'sifuri') def test_1_to_9(self): self.assertEqual(num2words(1, lang='sw'), 'moja') self.assertEqual(num2words(2, lang='sw'), 'mbili') + self.assertEqual(num2words(3, lang='sw'), 'tatu') + self.assertEqual(num2words(4, lang='sw'), 'nne') + self.assertEqual(num2words(5, lang='sw'), 'tano') + self.assertEqual(num2words(6, lang='sw'), 'sita') + self.assertEqual(num2words(7, lang='sw'), 'saba') + self.assertEqual(num2words(8, lang='sw'), 'nane') self.assertEqual(num2words(9, lang='sw'), 'tisa') def test_tens_10_to_99(self): @@ -47,10 +56,16 @@ def test_hundreds_100_to_999(self): self.assertEqual(num2words(700, lang='sw'), 'mia saba') self.assertEqual(num2words(999, lang='sw'), 'mia tisa tisini na tisa') - def test_thousands_1000_to_9999(self): + def test_thousands_1000_to_999999(self): self.assertEqual(num2words(1000, lang='sw'), 'elfu moja') + self.assertEqual(num2words(1001, lang='sw'), 'moja elfu na moja') self.assertEqual(num2words(7000, lang='sw'), 'elfu saba') self.assertEqual(num2words(9000, lang='sw'), 'elfu tisa') + self.assertEqual(num2words(1008, lang='sw'), 'moja elfu na nane') + self.assertEqual(num2words(7012, lang='sw'), 'saba elfu na kumi na mbili') + self.assertEqual( + num2words(7312, lang='sw'), 'saba elfu, mia tatu kumi na mbili' + ) self.assertEqual( num2words(1322, lang='sw'), 'moja elfu, mia tatu ishirini na mbili' ) @@ -60,3 +75,49 @@ def test_thousands_1000_to_9999(self): self.assertEqual( num2words(9999, lang='sw'), 'tisa elfu, mia tisa tisini na tisa' ) + self.assertEqual(num2words(99000, lang='sw'), 'tisini na tisa elfu') + self.assertEqual(num2words(99001, lang='sw'), 'tisini na tisa elfu na moja') + self.assertEqual(num2words(70_000, lang='sw'), 'sabini elfu') + self.assertEqual(num2words(70_001, lang='sw'), 'sabini elfu na moja') + self.assertEqual( + num2words(99_999, lang='sw'), + 'tisini na tisa elfu, mia tisa tisini na tisa', + ) + self.assertEqual(num2words(100_001, lang='sw'), 'mia moja elfu na moja') + self.assertEqual( + num2words(999_999, lang='sw'), + 'mia tisa tisini na tisa elfu, mia tisa tisini na tisa', + ) + + def test_big_numbers(self): + self.assertEqual(num2words(9_000_000, lang='sw'), 'milioni tisa') + self.assertEqual(num2words(9_000_001, lang='sw'), 'tisa milioni na moja') + self.assertEqual(num2words(9_000_007, lang='sw'), 'tisa milioni na saba') + self.assertEqual(num2words(9_000_010, lang='sw'), 'tisa milioni na kumi') + self.assertEqual( + num2words(9_000_317, lang='sw'), 'tisa milioni, mia tatu kumi na saba' + ) + self.assertEqual( + num2words(4_001_235, lang='sw'), + 'nne milioni, moja elfu, mia mbili thelathini na tano', + ) + self.assertEqual( + num2words(3_081_369, lang='sw'), + 'tatu milioni, themanini na moja elfu, mia tatu sitini na tisa', + ) + self.assertEqual( + num2words(1_581_317, lang='sw'), + 'moja milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + ) + self.assertEqual( + num2words(911_581_317, lang='sw'), + 'mia tisa kumi na moja milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + ) + self.assertEqual( + num2words(1_911_581_317, lang='sw'), + 'moja bilioni, mia tisa kumi na moja milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + ) + self.assertEqual( + num2words(882_911_581_317, lang='sw'), + 'mia nane themanini na mbili bilioni, mia tisa kumi na moja milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + ) From 44728f21010901c2eafd78f8a96918a664ff3cef Mon Sep 17 00:00:00 2001 From: Innocent Zenda Date: Wed, 27 Sep 2023 19:34:09 +0300 Subject: [PATCH 3/3] add to_ordinal method --- num2words/lang_SW.py | 30 ++++++++++++++++++++++++++---- tests/test_sw.py | 19 ++++++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/num2words/lang_SW.py b/num2words/lang_SW.py index e3e068ca..82d2ee71 100644 --- a/num2words/lang_SW.py +++ b/num2words/lang_SW.py @@ -113,20 +113,32 @@ def thousands_to_cardinal(self, number): return f'{ONES[thousands]} elfu na {self.tens_to_cardinal(remainder)}' elif thousands < 100: # thousands = 10 - 99 - return f'{self.tens_to_cardinal(thousands)} elfu na {self.tens_to_cardinal(remainder)}' + return ( + f'{self.tens_to_cardinal(thousands)} elfu na ' + f'{self.tens_to_cardinal(remainder)}' + ) elif thousands < 1000: # thousands = 100 - 999 - return f'{self.hundreds_to_cardinal(thousands)} elfu, {self.tens_to_cardinal(remainder)}' + return ( + f'{self.hundreds_to_cardinal(thousands)} elfu, ' + f'{self.tens_to_cardinal(remainder)}' + ) elif remainder < 1000: if thousands < 10: # thousands = 1 - 9 return f'{ONES[thousands]} elfu, {self.hundreds_to_cardinal(remainder)}' elif thousands < 100: # thousands = 10 - 99 - return f'{self.tens_to_cardinal(thousands)} elfu, {self.hundreds_to_cardinal(remainder)}' + return ( + f'{self.tens_to_cardinal(thousands)} elfu, ' + f'{self.hundreds_to_cardinal(remainder)}' + ) else: # thousands = 100 - 999 - return f'{self.hundreds_to_cardinal(thousands)} elfu, {self.hundreds_to_cardinal(remainder)}' + return ( + f'{self.hundreds_to_cardinal(thousands)} elfu, ' + f'{self.hundreds_to_cardinal(remainder)}' + ) def big_number_to_cardinal(self, number): digits = [c for c in str(number)] @@ -180,3 +192,13 @@ def to_cardinal(self, number): # 1_000_000 - Infinity string = self.big_number_to_cardinal(int(number)) return string + + def to_ordinal(self, number): + '''Converts number to ordinal.''' + if number <= 0: + raise Exception('number must be greater than zero to convert to ordinal') + if number == 1: + return 'kwanza' + if number == 2: + return 'pili' + return self.to_cardinal(number) diff --git a/tests/test_sw.py b/tests/test_sw.py index 1d22c34b..ebed74aa 100644 --- a/tests/test_sw.py +++ b/tests/test_sw.py @@ -111,13 +111,26 @@ def test_big_numbers(self): ) self.assertEqual( num2words(911_581_317, lang='sw'), - 'mia tisa kumi na moja milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + 'mia tisa kumi na moja milioni, mia tano themanini na moja elfu, ' + 'mia tatu kumi na saba', ) self.assertEqual( num2words(1_911_581_317, lang='sw'), - 'moja bilioni, mia tisa kumi na moja milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + 'moja bilioni, mia tisa kumi na moja milioni, mia tano themanini ' + 'na moja elfu, mia tatu kumi na saba', ) self.assertEqual( num2words(882_911_581_317, lang='sw'), - 'mia nane themanini na mbili bilioni, mia tisa kumi na moja milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + 'mia nane themanini na mbili bilioni, mia tisa kumi na moja ' + 'milioni, mia tano themanini na moja elfu, mia tatu kumi na saba', + ) + + def test_number_to_ordinal(self): + self.assertEqual(num2words(1, ordinal=True, lang='sw'), 'kwanza') + self.assertEqual(num2words(2, ordinal=True, lang='sw'), 'pili') + self.assertEqual(num2words(3, ordinal=True, lang='sw'), 'tatu') + self.assertEqual(num2words(9, ordinal=True, lang='sw'), 'tisa') + self.assertEqual(num2words(12, ordinal=True, lang='sw'), 'kumi na mbili') + self.assertRaises( + Exception, 'number must be greater than zero to convert to ordinal' )