Turning numbers into a string of words - challenge

I just completed this challenge at CodeEval.

You are given a positive integer number. This represents the sales made that day in your department store. The payables department however, needs this printed out in english. NOTE: The correct spelling of 40 is Forty. (NOT Fourty)

My solution works, and it works pretty well. However I think there has to be an easier way to do this:

import sys

def nums_to_words(number):
units = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
teens = ['', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen',
'seventeen', 'eighteen', 'nineteen']
tens = ['', 'ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy',
'eighty', 'ninety']
thousands = ['', 'thousand', 'million', 'billion', 'trillion', 'quadrillion',
'quintillion', 'sextillion', 'septillion', 'octillion',
'nonillion', 'decillion', 'undecillion', 'duodecillion',
'tredecillion', 'quattuordecillion', 'sexdecillion',
'septendecillion', 'octodecillion', 'novemdecillion',
'vigintillion']
words = []

if number == 0:
words.append("zero")
else:
number_string = "%d" % number
length_of_number_string = len(number_string)
number_groups = (length_of_number_string + 2) / 3
new_number_string = number_string.zfill(number_groups * 3)
for i in range(0, number_groups * 3, 3):
x, y, z = int(new_number_string[i]), int(new_number_string[i + 1]), int(new_number_string[i + 2])
w = number_groups - (i / 3 + 1)
if x >= 1:
words.append(units[x])
words.append("hundred")
if y > 1:
words.append(tens[y])
if z >= 1:
words.append(units[z])
elif y == 1:
if z >= 1:
words.append(teens[z])
else:
words.append(tens[y])
else:
if z >= 1:
words.append(units[z])
if (w >= 1) and ((x + y + z) > 0):
words.append(thousands[w])

name = []

for word in words:
name.append(word.title())

return ''.join(name) + "Dollars"

if __name__ == '__main__':
with open(sys.argv[1], "r") as numbers:
print(nums_to_words(int(num)))


Is there anything that is obviously noticeable that I can do better?

1. I think the code in the post is fine: the English language rules are quite complicated, and it's going to take a certain amount of computing effort however you implement it.

2. The variables units, teens, tens, and so on are always the same, so they could be global constants, rather than being constructed again every time the function is called.

3. It's conventional in Python for constants to have names written in all capital letters (see PEP8), so UNITS instead of units.

UNITS = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']


I think this is easier to read:

UNITS = 'zero one two three four five six seven eight nine'.split()

5. It's convenient to combine UNITS and TEENS into a single list giving all the names for numbers less than 20.

6. The code reads a string from standard input, converts it to an integer (by calling int(num), then converts the number back to a string (by calling "%d" % number), then splits the string into pieces, and converts each piece back to a number (by calling int(new_number_string[i])). This back-and-forth conversion is unnecessary, because you can split a number into 3-digit groups by repeatedly calling divmod until you get down to zero:

>>> divmod(123456789, 1000)
(123456, 789)
>>> divmod(123456, 1000)
(123, 456)
>>> divmod(123, 1000)
(0, 123)

7. The function nums_to_words does two things: (i) it converts a number to English words; and (ii) it capitalizes the words, joins them and appends "Dollars". The principle of separation of concerns suggests that these should be two functions. And that would be useful, because then the function implementing part (i) could call itself recursively.

It's also slightly more convenient to generate the results (by calling yield or yield from) instead of constructing a list. Then you could write this for concern (i):

UNITS = """
zero one two three four five six seven eight nine ten eleven
twelve thirteen fourteen fifteen sixteen seventeen eighteen
nineteen
""".split()
TENS = "zero ten twenty thirty forty fifty sixty seventy eighty ninety".split()
THOUSANDS = """
zero thousand million billion trillion quadrillion quintillion
sextillion septillion octillion nonillion decillion undecillion
duodecillion tredecillion quattuordecillion sexdecillion
septendecillion octodecillion novemdecillion vigintillion
""".split()

def english_words(n):
"""Generate words (excluding "and") naming the non-negative number n
in English.

"""
if n < 20:
yield UNITS[n]
elif n < 100:
tens, units = divmod(n, 10)
yield TENS[tens]
if units:
yield UNITS[units]
elif n < 1000:
hundreds, remainder = divmod(n, 100)
yield UNITS[hundreds]
yield "hundred"
if remainder:
yield from english_words(remainder)
else:
groups = []             # Groups of three digits from n.
while n:
n, group = divmod(n, 1000)
groups.append(group)
for i, group in reversed(list(enumerate(groups))):
if group:
yield from english_words(group)
if i:
yield THOUSANDS[i]


and this for concern (ii):

def num_to_words(n):
return ''.join(w.title() for w in english_words(n)) + "Dollars"


This is another way that you could solve it. Using the patterns inherent in the 'tricky' numbers (teens, twenties, etc...) the majority required strings can be constructed on the fly, rather than precomputed. For instance, for the number 30, the tens string is 'thirty', which is the first two characters of the string representation of 3-th- plus the tens modifier for 3-ir-plus the end ty. This general pattern holds true for number from 20 to 99, and a similar one is true for 13,15,16,17,18, and 19 (not 14 because of the for/four spelling issue) with the teen ending. The rest of the digits of the number (100s etc...) are starightforward to solve.

def toString(number):
numbers={1:'one',2:'two',3:'three',4:'four',5:'five',6:'six',7:'seven',8:'eight',9:'nine'}
teens={0:'ten ',1:'eleven ',2:'twelve ',4:'fourteen '}
modifiers_tens={2:'en',3:'ir',4:'r',5:'f',6:'x',7:'ven',8:'gh',9:'ne'}
positions={2:'hundred and ', 3:'thousand ', 4:'million ', 5:'billion ', 6:'trillion ', 7:'quadrillion ',
8:'quintillion ', 9:'sextillion ', 10:'septillion ', 11:'octillion ',
12:'nonillion ', 13:'decillion ', 14:'undecillion ', 15:'duodecillion ',
16:'tredecillion ', 17:'quattuordecillion ', 18:'sexdecillion ',
19:'septendecillion ', 20:'octodecillion ', 21:'novemdecillion ',
22:'vigintillion '}
number_str=str(number)
number_list=list(number_str)[::-1]#Reverse input order to parse number in ascending powers of 10, rather than descending
if len(number_list)==1:#Handle the trivial case
try:#Handle special 10s digit cases
if int(number_list[1])==1:#If the number is a teen
answer.append(teens[int(number_list[0])])#Try to use string from precomputed teen numbers that don't follow a pattern.
else:#If the 10s digit is not 1
answer.append(numbers[int(number_list[0])])#Append the ones digit, since it has no effect on the 10s digit
answer.append(numbers[int(number_list[1])][:2]+modifiers_tens[int(number_list[1])]+'ty ')#Construct the 10s digit using the pattern
except KeyError:#If the teen value had not been precomputed, construct it using the pattern.