Repository: TheAlgorithms/Python

Issue:
`web_programming/convert_number_to_words.py` is not web programming
### What would you like to share?

[`web_programming/convert_number_to_words.py`](https://github.com/TheAlgorithms/Python/blob/5aefc00f0f1c692ce772ddbc616d7cd91233236b/web_programming/convert_number_to_words.py) has no web programming whatsoever. It should be moved to a different directory.

### Additional information

#8877 shows that this file is among those which broke ruff following the most recent pre-commit update. With how heavily nested the code is (among other questionable coding choices), perhaps it should be rewritten as well.


Discussion:
similar result can be achieved using inflect library in python



> similar result can be achieved using inflect library in python

@ARUSHI-2004 No, you misunderstand. The entire point of this repo is to implement algorithms _from scratch_ for educational purposes. Using an existing Python library that implements the entire algorithm for us would defeat the whole point.
If `web_programming` is the wrong category, what is the right category?

@ARUSHI-2004 idea might be quite instructional for 1. testing and 2. benchmarks.
> If `web_programming` is the wrong category, what is the right category?

@cclauss [`conversions`](https://github.com/TheAlgorithms/Python/tree/master/conversions), it's in the name: `convert_number_to_words.py`
> > If `web_programming` is the wrong category, what is the right category?
> 
> @cclauss [`conversions`](https://github.com/TheAlgorithms/Python/tree/master/conversions), it's in the name: `convert_number_to_words.py`

how ironic

Resolving patch:
diff --git a/conversions/convert_number_to_words.py b/conversions/convert_number_to_words.py
new file mode 100644
--- /dev/null
+++ b/conversions/convert_number_to_words.py
@@ -0,0 +1,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 = {
+        0: "",
+        1: "one",
+        2: "two",
+        3: "three",
+        4: "four",
+        5: "five",
+        6: "six",
+        7: "seven",
+        8: "eight",
+        9: "nine",
+    }
+
+    TEENS: ClassVar = {
+        0: "ten",
+        1: "eleven",
+        2: "twelve",
+        3: "thirteen",
+        4: "fourteen",
+        5: "fifteen",
+        6: "sixteen",
+        7: "seventeen",
+        8: "eighteen",
+        9: "nineteen",
+    }
+
+    TENS: ClassVar = {
+        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) = }")
diff --git a/web_programming/convert_number_to_words.py b/web_programming/convert_number_to_words.py
deleted file mode 100644
--- a/web_programming/convert_number_to_words.py
+++ /dev/null
@@ -1,109 +0,0 @@
-import math
-
-
-def convert(number: int) -> str:
-    """
-    Given a number return the number in words.
-
-    >>> convert(123)
-    'OneHundred,TwentyThree'
-    """
-    if number == 0:
-        words = "Zero"
-        return words
-    else:
-        digits = math.log10(number)
-        digits = digits + 1
-        singles = {}
-        singles[0] = ""
-        singles[1] = "One"
-        singles[2] = "Two"
-        singles[3] = "Three"
-        singles[4] = "Four"
-        singles[5] = "Five"
-        singles[6] = "Six"
-        singles[7] = "Seven"
-        singles[8] = "Eight"
-        singles[9] = "Nine"
-
-        doubles = {}
-        doubles[0] = ""
-        doubles[2] = "Twenty"
-        doubles[3] = "Thirty"
-        doubles[4] = "Forty"
-        doubles[5] = "Fifty"
-        doubles[6] = "Sixty"
-        doubles[7] = "Seventy"
-        doubles[8] = "Eighty"
-        doubles[9] = "Ninety"
-
-        teens = {}
-        teens[0] = "Ten"
-        teens[1] = "Eleven"
-        teens[2] = "Twelve"
-        teens[3] = "Thirteen"
-        teens[4] = "Fourteen"
-        teens[5] = "Fifteen"
-        teens[6] = "Sixteen"
-        teens[7] = "Seventeen"
-        teens[8] = "Eighteen"
-        teens[9] = "Nineteen"
-
-        placevalue = {}
-        placevalue[2] = "Hundred,"
-        placevalue[3] = "Thousand,"
-        placevalue[5] = "Lakh,"
-        placevalue[7] = "Crore,"
-
-        temp_num = number
-        words = ""
-        counter = 0
-        digits = int(digits)
-        while counter < digits:
-            current = temp_num % 10
-            if counter % 2 == 0:
-                addition = ""
-                if counter in placevalue and current != 0:
-                    addition = placevalue[counter]
-                if counter == 2:
-                    words = singles[current] + addition + words
-                elif counter == 0:
-                    if ((temp_num % 100) // 10) == 1:
-                        words = teens[current] + addition + words
-                        temp_num = temp_num // 10
-                        counter += 1
-                    else:
-                        words = singles[current] + addition + words
-
-                else:
-                    words = doubles[current] + addition + words
-
-            else:
-                if counter == 1:
-                    if current == 1:
-                        words = teens[number % 10] + words
-                    else:
-                        addition = ""
-                        if counter in placevalue:
-                            addition = placevalue[counter]
-                        words = doubles[current] + addition + words
-                else:
-                    addition = ""
-                    if counter in placevalue:
-                        if current != 0 and ((temp_num % 100) // 10) != 0:
-                            addition = placevalue[counter]
-                    if ((temp_num % 100) // 10) == 1:
-                        words = teens[current] + addition + words
-                        temp_num = temp_num // 10
-                        counter += 1
-                    else:
-                        words = singles[current] + addition + words
-            counter += 1
-            temp_num = temp_num // 10
-    return words
-
-
-if __name__ == "__main__":
-    import doctest
-
-    doctest.testmod()


Test patch: