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 &gt;= 1:
                words.append(units[x])
                words.append("hundred")
            if y &gt; 1:
                words.append(tens[y])
                if z &gt;= 1:
                    words.append(units[z])
            elif y == 1:
                if z &gt;= 1:
                    words.append(teens[z])
                else:
                    words.append(tens[y])
            else:
                if z &gt;= 1:
                    words.append(units[z])
            if (w &gt;= 1) and ((x + y + z) &gt; 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:
        for num in numbers.readlines():
            print(nums_to_words(int(num)))


Is there anything that is obviously noticeable that I can do better?
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.
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.
It's conventional in Python for constants to have names written in all capital letters (see PEP8), so UNITS instead of units.
Instead of writing:

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()

It's convenient to combine UNITS and TEENS into a single list giving all the names for numbers less than 20.
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:

&gt;&gt;&gt; divmod(123456789, 1000)
(123456, 789)
&gt;&gt;&gt; divmod(123456, 1000)
(123, 456)
&gt;&gt;&gt; divmod(123, 1000)
(0, 123)

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 &lt; 20:
        yield UNITS[n]
    elif n &lt; 100:
        tens, units = divmod(n, 10)
        yield TENS[tens]
        if units:
            yield UNITS[units]
    elif n &lt; 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"