4
\$\begingroup\$

This code seems to work fine but I am sure this reads very amateurish. I would appreciate your comments as to how to make this code more "professional":

def nToTxt(n):
    nums = {0:"", 1:'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six', 7: 
   'seven', 8: 'eight', 9: 'nine', 10: 'ten',11: 'eleven', 12: 'twevlve', 
    13: 'thirteen', 14: 'fourteen', 15: 'fifteen', 16: 'sixteen', 17: 'seventeen', 
    18: 'eighteen', 19 : 'ninteen'}
    tens = {2:'twenty', 3: 'thirty', 4: 'forty', 5: 'fifty', 6: 'sixty', 7: 'seventy', 
    8: 'eighty', 9: 'ninty'}


    if n < 21 :
        txt = f"{nums[n]}"
    elif n > 20 and n< 100:
        txt = f"{tens[int(n/10)]} {nums[n%10]}"
    else:
        txt = f"{nums[n//100]} hundred {tens[(int(n/10))%10]} {nums[n%10]}"

    return(txt)


z=124326859660
x = f"{z:012}" # format number by padding with zeros to fit a 100 billion number (12 digits)
billions = int(x[:3])
millions =int(x[3:6])
thousands = int (x[6:9])
digits = int(x[9:])


if billions > 0:
    print(f"{nToTxt(billions)} billion {nToTxt(millions)} million {nToTxt(thousands)} thousand {nToTxt(digits)}")
elif millions > 0:
    print(f"{nToTxt(millions)} million {nToTxt(thousands)}thousand {nToTxt(digits)}")   
elif thousands > 0:
    print(f"{nToTxt(thousands)} thousand {nToTxt(digits)}")
else:
    print(f"{nToTxt(digits)}")
\$\endgroup\$
3
  • 2
    \$\begingroup\$ Please check your code for spelling errors first: "ninty" Also, {nToTxt(thousands)}thousand is missing a space. Did you even test this at all? Did you even write tests for it? \$\endgroup\$
    – BCdotWEB
    Commented Dec 18, 2022 at 9:23
  • 2
    \$\begingroup\$ Don’t re-invent the wheel. Use a module like num2word. \$\endgroup\$
    – agtoever
    Commented Dec 18, 2022 at 9:28
  • 2
    \$\begingroup\$ Welcome to Code Review. I have rolled back your edits. Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$
    – Heslacher
    Commented Dec 19, 2022 at 5:57

3 Answers 3

7
\$\begingroup\$

The "professional" approach would be using libraries that already exist. As agtoever mentioned, there's num2words. So you can do things like:

>>> from num2words import num2words
>>> num2words(42)
forty-two
>>> num2words(42, to='ordinal')
forty-second
>>> num2words(42, lang='fr')
quarante-deux

This library already supports thousand-separators, multiple languages, currencies and other neat stuff you hadn't even considered supporting yet.

As BCdotWEB indicated in the comments, you should check your spelling (ninteen -> nineteen, ninty -> ninety) and write tests. Projects like these are perfect to familiarize yourself with Test Driven Development (TDD). TDD can very roughly be described as 'write tests first, keep writing code till tests pass, repeat`.

Considering you already have the code, I'll cut away portions of it and use the base to somewhat illustrate TDD. We're already doing it the wrong way around (there's code first and tests later instead of vice versa), but I think it gets the message across:

def nToTxt(n):
    nums = {0:"", 1:'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six', 7: 
   'seven', 8: 'eight', 9: 'nine', 10: 'ten',11: 'eleven', 12: 'twevlve', 
    13: 'thirteen', 14: 'fourteen', 15: 'fifteen', 16: 'sixteen', 17: 'seventeen', 
    18: 'eighteen', 19 : 'nineteen'}
    tens = {2:'twenty', 3: 'thirty', 4: 'forty', 5: 'fifty', 6: 'sixty', 7: 'seventy', 
    8: 'eighty', 9: 'ninety'}


    if n < 21 :
        txt = f"{nums[n]}"
    elif n > 20 and n< 100:
        txt = f"{tens[int(n/10)]} {nums[n%10]}"
    else:
        txt = f"{nums[n//100]} hundred {tens[(int(n/10))%10]} {nums[n%10]}"

    return(txt)

assert nToTxt(1) == "one"
assert nToTxt(2) == "two"
assert nToTxt(7) == "seven"
assert nToTxt(10) == "ten"
assert nToTxt(19) == "nineteen"
# assert nToTxt(20) == "twenty"
assert nToTxt(21) == "twenty one"
assert nToTxt(22) == "twenty two"
assert nToTxt(24) == "twenty four"
assert nToTxt(28) == "twenty eight"
# assert nToTxt(30) == "thirty"
assert nToTxt(49) == "forty nine"
assert nToTxt(81) == "eighty one"

This is not the best way to write such tests, but it is the easiest way. Especially if you've never written tests before.

assert checks if the following statement is an expression and whether the expression resolves to True. Would I for example change the test for 49 to "fourty nine" instead of "forty nine", an AssertionError is raised.

The tests for 20 and 30 are commented out. The other tests pass, but those do not. There's a problem in the core of your nToTxt function that fails to handle this case. There are multiple problems here. For starters:

if n < 21 :

Is still true for 20. It's handled the same way 10 is, but 20 is not in the same nums dictionary. So this will throw a KeyError, the program tells you it can not find the entry you're looking for. So, we'll change the program to:

if n < 20 :
    txt = f"{nums[n]}"
elif n >= 20 and n < 100:
    txt = f"{tens[int(n/10)]} {nums[n%10]}"

This is still wrong. If there are no lookups for nums remaining, there will still be a trailing space. There are multiple ways to handle this. The two obvious ones are removing the trailing spaces or modifying the string in case n%10 is 0.

First approach:

if n < 20 :
    txt = f"{nums[n]}"
elif n >= 20 and n < 100:
    txt = f"{tens[int(n/10)]} {nums[n%10]}"
else:
    txt = f"{nums[n//100]} hundred {tens[(int(n/10))%10]} {nums[n%10]}"

return(txt.rstrip(" "))

str.rstrip([chars]) will return a copy of the string with trailing characters removed. There's an lstrip too, in case you ever want to remove characters from the front of the string.

I'll leave the second approach as an exercise, but you'll want to overhaul the remainder of the function either way. At the moment, nToTxt(0) is empty. Modifying the dictionary to have it contain zero will break the rest of your program. Add zero and modify the rest of your program to handle it properly with extra checks.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks so much. Learned a lot from your answer. I also found that I have to be a lot more careful. I now re-tested and found that I also need 0 and 1 in the def ntotxt(n)-> tens to print "" (in addition to those you pointed. Thanks again. \$\endgroup\$
    – Areng
    Commented Dec 18, 2022 at 15:03
  • \$\begingroup\$ I have cleaned up the code. Hope this works correctly now. I have a couple of questions: (a) If I do include tests with the assert statements, do I leave them in the code as a commented-out block? (b) Also, I can only test up to 999 by passing the n_to_txt to the assert command. But the final print comes from the print statements. How are these tested in real life? Thanks. \$\endgroup\$
    – Areng
    Commented Dec 19, 2022 at 0:56
1
\$\begingroup\$

Just coming in to add that there is an as-yet unmentioned bug in the code.

if billions > 0:
    print(f"{nToTxt(billions)} billion {nToTxt(millions)} million {nToTxt(thousands)} thousand {nToTxt(digits)}")

In combination with nums = {0:"", means that, if the input is exactly one billion, 1000000000, it will print "one billion million thousand "

\$\endgroup\$
2
  • \$\begingroup\$ I know. I changed my code and posted the corrected code, but someone rolled back the edits and so this wrong code is here with all the errors. Should I post the correct code in a comment? I really hate leaving this code as is, but at the same time want to follow the rules of this site. \$\endgroup\$
    – Areng
    Commented Dec 28, 2022 at 2:58
  • 2
    \$\begingroup\$ @Areng another user left a comment on your original question with this link. It goes over what can be done after an answer is given to a question and some best practices - I recommend giving it a read. This is so others having the same issue as you can, in the future, see your question and the answers given - rather than never seeing code with the same problem(s) they are facing. It's a Question + Answer style, rather than a forum. \$\endgroup\$ Commented Dec 28, 2022 at 19:06
0
\$\begingroup\$

I found some errors in my original post: for example, it did not print round numbers correctly in addition to some issues others pointed out. The following code fixes logic errors. Also I changed the print statement to ' '.join(txt.split()). This removes multiple spaces printed when thousands, hundreds etc. are zero and keeps just a single space between words.

def n_to_txt(n):

    nums = {
        0: "", 1: "one", 2: "two", 3: "three",4: "four", 5: "five", 6: "six", 7: "seven", 8: "eight",
        9: "nine",10: "ten",11: "eleven",12: "twelve", 13: "thirteen",14: "fourteen",
        15: "fifteen",16: "sixteen",17: "seventeen",18: "eighteen",19: "nineteen",
    }
    tens = {
        0: "", 1: "", 2: "twenty", 3: "thirty",  4: "forty", 5: "fifty",6: "sixty",7: "seventy", 8: "eighty", 9: "ninety",
    }

    if n < 20:
        txt = f"{nums[n]}"
    elif n >= 20 and n < 100:
        txt = f"{tens[int(n/10)].rstrip(' ')} {nums[n%10]}"
    elif n >= 100 and n % 100 > 0 and n % 100 < 20:
        # between 1 and 19 check nums not tens
        txt = f"{nums[n//100]} hundred {nums[n%100]}"
    else:
        txt = f"{nums[n//100]} hundred {tens[(int(n/10))%10]} {nums[n%10]}"
    return txt.strip()


z = 1000000021  # z could come from something, need not be an input
if z == 0:
    print("zero")
elif z > 999999999999:
    print("Number is too large. This is only to practice Python. ")
else:

    x = f"{z:012}"  # format number by padding with zeros to fit a 100 billion number (12 digits)
    billions = int(x[:3])
    millions = int(x[3:6])
    thousands = int(x[6:9])
    digits = int(x[9:])

    if billions > 0: 
       txt =  f"{n_to_txt(billions)} billion {n_to_txt(millions)}{'million' if millions > 0  else ''}{n_to_txt(thousands)} {'thousand' if thousands > 0  else ''} {n_to_txt(digits)}"

    elif millions > 0:
        txt = f"{n_to_txt(millions)} million {n_to_txt(thousands)}{'thousand' if thousands > 0  else ''} {n_to_txt(digits)}"
        
    elif thousands > 0:
        txt = f"{n_to_txt(thousands)} thousand {n_to_txt(digits)}"
    else:
         txt = f"{n_to_txt(digits)}"

    print( ' '.join(txt.split()))
\$\endgroup\$
1
  • \$\begingroup\$ Rather than answer your own question, it would probably be better to write a follow up question with your improvements and a link back to this question. That is probably what you should have done when you tried to edit the original question with corrections from the answer. \$\endgroup\$
    – pacmaninbw
    Commented Dec 29, 2022 at 14:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.