tags:

views:

266

answers:

6
import sys

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):
    return ' '.join([words[int(number[0])],  'hundred',  convertDuo(number[1:3])]) # convertDuo(number[1:3])


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


if __name__ == "__main__":

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

    if int(numeral) < 100:
        print convertDuo(numeral)
    else:

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

        numeralSegments.reverse()

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

Warning: I'm a total python novice. I'm aware there are probably many times more efficient ways of doing things. I'd appreciate any pointers to them.

Edit: The code currently only works for numbers whose digit counts are multiples of three. I'd appreciate a suggestion for an elegant way to fix that as well. Thanks.

+3  A: 

Two improvements come to mind:

  • 40 is spelled "forty", not "fourty"
  • your program needs unit tests

Have a look at the Python doctest and unittest modules.

Greg Hewgill
hehe, i wrote that note down on my paper, and *still* got it wrong. :(
Karan
+1  A: 

Maybe Numbers and plural words as spoken English will help a little. A little dated though - 4 May 2005.

gimel
+2  A: 

You can't group digits into "segments" going from left-to-right. The range(0,len(),3) is not going to work out well. You'll have to write the same algorithm for inserting digit separators. You start from the right, picking off segments of digits.

What's left over (on the left, get it?) will be 1, 2 or 3 digits. You've got convertTrio and convertDuo, which handle 3 and 2 digits, respectively. Somewhere in there is a convert one digit function (can't see it).

If it's not homework, then, here's a proper digit clustering algorithm

def segment( n ):
   segList= []
   while len(n) > 3:
       segList.insert( 0, n[-3:] )
       n= n[:-3]
   segList.insert( 0, n )
   return segList

Edit

To be more Pythonic, package this as a tidy, reusable module. The stuff inside the if __name__ == "__main__" does two things, which should be separated.

Your command-line parsing (anything having to do with sys.argv is one thing. The actual "convert a number" function is something else entirely. You want to look more like this.

if __name__ == "__main__":
    import sys
    for number in sys.argv[1:]:
        print number2string( number )

Then, your number2string function becomes an easily reused piece of this module.

S.Lott
Thanks. do you have any comments on the general 'python-ness' of the code i've written. Are there things i should have done in a more functional style perhaps? I'm used to very strict oop languages like Java and C#. Thanks
Karan
`number2string` reminds me about `str(number)` e.i. the name poorly communicates the intention of the function. IMO perl's `spell_number` is a better name in this case.
J.F. Sebastian
@J.F. Sebastion: Sorry, but "spell_number" is opaque to me. Naming is subjective, there's no "clearer", just "what I prefer". When teaching intro-to-linux there's always someone who objects to "rm" removing a file. They want to spell remove, "del". Don't know why.
S.Lott
@S.Lott: I think the problem is that "string" is very generic, and doesn't tell you much about what form that string takes - "42" is a string representation as much as "forty two" is. spell_number is a bit unrevealing too though - I'd go for numberToWords()
Brian
A: 

Check out source for Number::Spell Perl module. It is short and can be easily ported to Python (if it has not already been done).

J.F. Sebastian
+2  A: 

Instead of slicing digits, use modular arithmetic to separate the units. This function will convert a number less than 100 using the given data structures.

def convert(n):
    q, r = divmod(n, 10)
    if q < 2:
     return words[n]
    result = tens[q-1] # offset because tens is missing first null value
    if r:
     result += '-' + words[r]
    return result

Then use convert recursively to support larger numbers, e.g., start with divmod(n, 100) and so on.

Coady
thanks, this is a good idea.
Karan
A: 

In case anyone reading this is looking for a numbers to words script, have a look at inflect.py

import inflect
p = inflect.engine()
p.numwords(123456789)

gives

'one hundred and twenty-three million, four hundred and fifty-six thousand, seven hundred and eighty-nine'
pwdyson