Welcome, guest | Sign In | My Account | Store | Cart

A simple python script that translates an integer to plain English.

Python, 83 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import sys, string as str

words = {
    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 = [
    '',
    'twenty',
    'thirty',
    'forty',
    'fifty',
    'sixty',
    'seventy',
    'eighty',
    'ninety',
]

placeholders = [
    '',
    'thousand',
    'million',
    'billion',
    'trillion',
    'quadrillion'
]

# segMag = segment magnitude (starting at 1)
def convertTrio(number):
    if int(number) < 100:
        return convertDuo(number[1:3])
    else:
        return ' '.join([ words[int(number[0])],  'hundred',  convertDuo(number[1:3]) ])


def convertDuo(number):
    #if teens or less
    if int(number[0]) <= 1:
        return words[int(number)]
    #twenty-five
    else:
        return ''.join([tens[int(number[0]) - 1], '-', words[int(number[1])]])


if __name__ == "__main__":

    string = []
    numeralSegments = []
    numeral = sys.argv[1]

    # left-pad number with zeros to make its length a multiple of 3
    if len(numeral) % 3 > 0:    
        numeral = str.zfill( numeral, (3 - (len(numeral) % 3)) + len(numeral) )

    # split number into lists, grouped in threes
    for i in range (len(numeral), 0, -3):
        numeralSegments.append(numeral[i-3:i])

    # for every segment, convert to trio word and append thousand, million, etc depending on magnitude
    for i in range (len(numeralSegments)):
        string.append(convertTrio(numeralSegments[i]) + ' ' + placeholders[i])

    # reverse the list of strings before concatenating to commas
    string.reverse()        
    print ', '.join(string)

critique appreciated

5 comments

h-kan 15 years, 4 months ago  # | flag

Typo on line 58?

sebastien.renard 15 years, 4 months ago  # | flag

Litle bug (using pyhon 2.4.5 on Linux) : python recipe-576577-1.py 100 Traceback (most recent call last): File "recipe-576577-1.py", line 99, in ? string.append(convertTrio(numeralSegments[i]) + ' ' + placeholders[i]) File "recipe-576577-1.py", line 71, in convertTrio return ' '.join([ words[int(number[0])], 'hundred', convertDuo(number[1:3]) ]) File "recipe-576577-1.py", line 77, in convertDuo return words[int(number)] KeyError: 0

David Lambert 15 years, 4 months ago  # | flag

I wrote one of these a couple years ago. It's 40% faster than num2words given a 10 digit number as input, and possibly has fewer errors. A clever programmer will extend this to work with decimal objects so that we can play with __format__ and __str__ in derived classes. Thanks.

'''
    >>> write_number(2)
    'two'
    >>> write_number(12000)
    'twelve thousand'
    #>>> write_number(decimal('0.07'))  # erg
    #'seven hundredths'
    #'seventy thousandths' is incorrect.  yuck.
'''

__all__ = 'write_number'.split()

zero_to_nineteen = 'zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen'.split()

decades = 'zero ten twenty thirty forty fifty sixty seventy eighty ninety'.split()

ten_to_the_3n = 'ones thousand million billion trillion quadrillion quintillion sextillion septillion octillion nonillion decillion undecillion duodecillion tredecillion quattuordecillion quindecillion sexdecillion septendecillion octodecillion novemdecillion vigintillion unvigintillion duovigintillion trevigintillion quattuorvigintillion quinvigintillion sexvigintillion septenvigintillion octovigintillion novemvigintillion triacontillion trigintillion untrigintillion duotrigintillion'.split()

def tens(n):
    if n < 20:
        if n == 0:
            return [decades[0],]
        return [zero_to_nineteen[n],]
    decade,remainder = divmod(n,10)
    rv = [decades[decade],]
    if remainder:
        rv.append(zero_to_nineteen[remainder])
    return ['-'.join(rv),]

def hundreds(n):
    n,remainder = divmod(n,100)
    if remainder:
        rv = tens(remainder)
    else:
        rv = []
    if n:
        rv[:0] = [zero_to_nineteen[n],'hundred']
    return rv

def thousands(n):
    rv = []
    p = 0
    while n:
        n,remainder = divmod(n,1000)
        if p < len(ten_to_the_3n):
            if remainder:
                rv[:0] = hundreds(remainder)+[ten_to_the_3n[p],]
        else:
            rv[:0] = ['really-huge',]
            return rv
        p += 1
    return rv

def write_number(n,negative='negative',positive=''):
    if not n:
        return zero_to_nineteen[0]
    if 0 < n:
        sign = positive
    else:
        n = -n
        sign = negative
    if n < 1000:
        rv = hundreds(n)
    else:
        rv = thousands(n)
    if sign:
        rv[:0] = [sign,]
    if rv[-1] == ten_to_the_3n[0]:
        del rv[-1]
    return' '.join(rv)
David Lambert 15 years, 4 months ago  # | flag

==> Logic error identified by sebastien.renard. Try numbers input like this---

$ python number_as_word.py 1000 $ python number_as_word.py 1000567

==> Include module __doc__ string that tells how to use program.

==> Well enough written that extending program to include the zero case was trivial.

$ python number_as_word.py 0

This reduces the impact of the logic error.

==> Write "if len(numeral) % 3:" instead of "if len(numeral) % 3 > 3:"

Would you write "if (a == b) is True:"? Well, that sort of thing is common but it doesn't work for me.

==> Don't change name of string module. string module is useful for its character lists, like punctuation. String objects contain the methods, as does the str type (or class, I use python 3). zfill thusly:

if len(numeral) % 3:
    numeral = numeral.zfill( (3 - (len(numeral) % 3)) + len(numeral) )

==> Avoid overwriting library module names or builtin names. I changed the name of the string variable.

==> Use comprehensions.

==> Write a function so the code can be used as a module.

For what it's worth, my version runs two and a half percent more slowly than does yours. Oh well!

'''
    The powerful module doc string:

    >>> print('{%s}'%(num2words(2342342324)))
    {two billion, three hundred forty-two million, three hundred forty-two thousand, three hundred twenty-four }

    $ python num2words.py 1231 23423
    one thousand, two hundred thirty-one 
    twenty-three thousand, four hundred twenty-three 
'''

import sys

words = {
    0 : 'zero',     # inserted zero
    1 : 'one',
    2 : 'two',
    3 : 'three',

...

def num2words(numeral):
    numeral = str(numeral)
    if len(numeral) % 3:
        numeral = numeral.zfill( (3 - (len(numeral) % 3)) + len(numeral) )
    numeralSegments = [numeral[i-3:i] for i in range (len(numeral), 0, -3)]
    group_names = [convertTrio(seg)+' '+placeholders[i]
                   for (i,seg) in enumerate(numeralSegments)]
    group_names.reverse()
    return ', '.join(group_names)
    #return ', '.join(reversed(group_names))

def main(*args):
    return '\n'.join([num2words(number) for number in args])
    #return '\n'.join(num2words(number) for number in args)

if __name__ == "__main__":
    print(main(*(sys.argv[1:] or ('',))))
k (author) 15 years, 3 months ago  # | flag

Thank you all for the excellent feedback. I'll make the suggested changes, but more importantly, you've given me ideas on how to become a better pythoner. Cheers!

Created by k on Sun, 30 Nov 2008 (MIT)
Python recipes (4591)
k's recipes (1)

Required Modules

Other Information and Tasks