16
\$\begingroup\$

I'm new to programming. I tried to find a way to convert numbers into letters with Python. I would like to receive some advice to improve myself. This program works for numbers between 1 and 106 and I would like to know how the logic of the program can be improved.

def changeNumberIntoLetter(value):
    number=numToLetter(value)
    return number

def numToLetter(value): #The function converts the numbers into letters. 
    if value==1: return 'one'
    elif value==2: return 'two'
    elif value==3: return 'three'
    elif value==4: return 'four'
    elif value==5: return 'five'
    elif value==6: return 'six'
    elif value==7: return 'seven'
    elif value==8: return 'eight'
    elif value==9: return 'nine'
    elif value==10: return 'ten'
    elif value==11: return 'eleven'
    elif value==12: return 'twelve'
    elif value==13: return 'thirteen'
    elif 13<value<=19: return composeTeen(value)
    elif value>19:
        if value==20: return 'twenty'
        elif value==30: return 'thirty'
        elif value==50: return 'fifty'
        elif value==10**2: return 'one hundred'
        elif value==10**3: return 'one thousand'
        elif value==10**5: return 'one hundred thousand'
        elif value==10**6: return 'one milion'
        elif value>=20: return composeNumbers(value)
        else: exit('Out of range')
    else: return ''

def composeNumbers(value):   #The function build every number biger than 40 
    if 40<=value<10**2:
        value1=int(str(value)[0])
        value2= int(str(value)[1])
        if value1==2: 
           value1= 'twen'
           return value1 + 'ty' + '-' + numToLetter(value2)
        if value1==3: 
           value1='thir'
           return value1 + 'ty' + '-' + numToLetter(value2)
        if value1==8: 
            value1='eigh'
            return value1 + 'ty' + '-' + numToLetter(value2)
        elif value1==5: 
            value1='fif'
            return value1 + 'ty' + '-' + numToLetter(value2)
        return numToLetter(value1) + 'ty' + '-' + numToLetter(value2)        
    elif 10**2<=value<10**3:
        value1=int(str(value)[0])
        value2= int(str(value)[1:])
        return numToLetter(value1) + ' ' + 'hundred' + ' ' + numToLetter(value2)
    elif 10**3<=value<10**4:
        value1=int(str(value)[0])
        value2=int(str(value)[1:]) 
    elif 10**4<=value<10**5:
        value1=int(str(value)[0:2])
        value2=int(str(value)[2:])
    elif 10**5<=value<10**6:
        value1=int(str(value)[0:3])
        value2=int(str(value)[3:])
    return numToLetter(value1) + ' ' + 'thousand' + ' ' + numToLetter(value2)



def composeTeen(value): #The function takes the unit and then converts it into letter to build the word.
    value= int(str(value)[-1])  #It turns elem in string to take the last position and it converts it again in integer to change it in letters. Then it composes the word adding 'teen' at the end.
    value= numToLetter(value) 
    if value=='five': value= 'fif'
    value= value + 'teen'
    return value
\$\endgroup\$
  • 1
    \$\begingroup\$ Something which could help you make this code effective is if you have a look at this code, codereview.stackexchange.com/questions/182833/…, it is not an answer because it doesn't do it just words but it uses techniques which could be good for what you are trying to do \$\endgroup\$ – 13ros27 Feb 22 at 17:59
17
\$\begingroup\$

This is good work! Well done as a beginner programmer.

def changeNumberIntoLetter(value):
    number=numToLetter(value)
    return number

This function doesn't really do anything useful to be honest. Can't you directly use numToLetter.

if value==1: return 'one'
elif value==2: return 'two'
elif value==3: return 'three'
elif value==4: return 'four'
elif value==5: return 'five'
elif value==6: return 'six'

Instead of creating lot of if statements. Try using a dictionary like this:

NUM_TO_WORD_MAPPING = {1: "one", 2: "two"}

and you can refer the string to number using

if value in NUM_TO_WORD_MAPPING:
    return NUM_TO_WORD_MAPPING[value]
else:
    # ... special cases.

As sergiy-kolodyazhnyy said, you can also use a list or a tuple.

int(str(value)[-1])

use value % 10 to extract numbers easily. This is modulo operator and returns remainder after division.


Bonus Content: Code Style

How your code looks like is as equally important as how you implement it. Because you and other programmers will spend time reading it.

“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write.”

― Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

Let's take a look at your function

def numToLetter(value): #The function converts the numbers into letters. 

I recommend that you use snake_case (number_to_letter) as it is considered industry standard for python programs. Comments are better done using """ comment """ style.

In this scenario however comment is not really required because your function name says that it's purpose is converting number to letter. People usually forget to update comments and it ends up giving incorrect information. If you want to use documentation comments anyway, make sure that comments are always up to date.

Reading material:

  1. PEP-8 - Coding conventions / standard
  2. PEP-287 - Document comment standard
  3. Documentation related SO Question
\$\endgroup\$
  • 1
    \$\begingroup\$ Thank you so much! This is really helpful; I'll improve the code with these changes \$\endgroup\$ – Roberta Belladonna Feb 22 at 20:17
  • 1
    \$\begingroup\$ A list or tuple could probably also work, with 0 item being None. A number itself would correspond to index position of the word item in list/tuple \$\endgroup\$ – Sergiy Kolodyazhnyy Feb 22 at 20:34
  • \$\begingroup\$ @RobertaBelladonna, don't forget to accept this answer if you think it answered your question \$\endgroup\$ – Albert Rothman Feb 22 at 22:30
  • 1
    \$\begingroup\$ Updated with more info. @SergiyKolodyazhnyy yes you are correct. :) \$\endgroup\$ – 422_unprocessable_entity Feb 23 at 16:31
6
\$\begingroup\$

I did this by inflect pypi library:

import inflect

ig = inflect.engine()
print(ig.number_to_words(85))

Out:

eighty-five

[NOTE]:

Install inflect library by pip:

$ pip install inflect

Furthermore, I found this method too:

>>> import num2word
>>> num2word.to_card(15)
'fifteen'
>>> num2word.to_card(55)
'fifty-five'
>>> num2word.to_card(1555)
'one thousand, five hundred and fifty-five'

[NOTE]:

These are the Github link of mentioned libraries if you want to know how they implemented:

\$\endgroup\$
  • 9
    \$\begingroup\$ Offloading to others is good, Pythonic even. But this doesn't really help the person improve their own programing abilities. \$\endgroup\$ – Peilonrayz Feb 22 at 18:26
  • 1
    \$\begingroup\$ I got it, so if he wants to know how these libraries implemented, I'll update my answer with the Github links of these libraries. \$\endgroup\$ – Benyamin Jafari Feb 22 at 18:34
  • 2
    \$\begingroup\$ @Peilonrayz - True, good point. I can see it both ways. While it doesn't help OP program the code themselves, it hopefully it shows OP that in Python, there's likely a library already out there to help you so you don't reinvent the wheel (no pun intended!). \$\endgroup\$ – BruceWayne Feb 22 at 19:46
2
\$\begingroup\$

Process

I'm relatively new to Python, so this may not be the Pythonic approach, but I think it's useful to use test-driven development for problems like this. Spelling out numbers has lots of special cases, which can be overwhelming, so working incrementally can be one way to get a handle on it.

I started by writing:

def main():
  for i in range(0, 20):
    print(f"{i}: {spellNumber(i)}")

This isn't literally a test, because it doesn't check the result, but it shows me the output for the first 20 integers, which is where many of the special cases are.

Then I wrote a simple implementation that uses an array to solve it:

def spellNumber(n):
  units = ["zero", "one", "two", "three", "four", "five",
           "six", "seven", "eight", "nine", "ten", "eleven",
           "twelve", "thirteen", "fourteen", "fifteen",
           "sixteen", "seventeen", "eighteen", "nineteen"]
  if n < 20: return units[n]
  return "out of range"

Once that appeared to be working, I extended my "test" by sampling values up to 100:

  for i in range(20, 100, 12):
    print(f"{i}: {spellNumber(i)}")

Note that I kept the original part of the test, so that I make sure I don't break the part that was already working when I try to extend it.

As I modified spellNumber to handle each new range of inputs, I sometimes adjusted the code I'd already written to make it easier for the new code to use it. Having all the tests helped me make sure I didn't break the older code in the process.

Along the way, I made mistakes, and had to figure out how to fix outputs like "thirty-zero" in a way that still allowed for "zero."

Style

Comments and docstrings

Your comments on what the functions do are useful, but I think the Pythonic way to document a function is to use a docstring. So, instead of:

def myFunction(): # This is what my function does

Write:

def myFunction():
  """This is what my function does."""

It's appropriate to use regular comments for details in your code, but comments that describe how a function (or class or file) is intended to be used should probably be docstrings.

Cascades of if-statements

Whenever you have a lot of similarly structured if-statements (if this: blah elif that: blub elif other: ...), consider whether using a data structure would be a better way to represent the problem.

For example, I used a table for the small numbers. Another answer had a good idea of using a dictionary in a similar way. A data structure you can index into or a table you can loop through will also be more efficient than a big cascade of if-statements.

More importantly, data is easy to modify and extend and greatly reduces the amount "logic" you have to worry about. For me separating the logic from the data, and made it easier to check the spelling of all those words, because they're all clustered nicely together instead of scattered across several helper functions.

\$\endgroup\$
  • \$\begingroup\$ Thank you for the exhaustive answer. But I don't understand why you use '12' in the second for-statement to expand the function: for i in range(20, 100, 12). \$\endgroup\$ – Roberta Belladonna Feb 23 at 11:28
  • 1
    \$\begingroup\$ @RobertaBelladonna It's the step value: The value to add to obtain the next value. In that case, the values obtained are 20, 32, 44, 56, 68, 80, and 92. \$\endgroup\$ – kiamlaluno Feb 23 at 16:36
  • 1
    \$\begingroup\$ @RobertaBelladonna: kiamlaluno is correct. I didn't want to print every single number up to 100, but I wanted a good sampling of them so that I would likely spot any bugs. I chose 12 as a step size because it gives a good sampling of the two digit numbers. If there were any bugs, they would likely show up. \$\endgroup\$ – Adrian McCarthy Feb 23 at 22:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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