Have you seen error reports that say "1 errors detected" or "2 error found" and thought there must be a better way?
There is!
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | _known = {
0: 'zero',
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',
20: 'twenty',
30: 'thirty',
40: 'forty',
50: 'fifty',
60: 'sixty',
70: 'seventy',
80: 'eighty',
90: 'ninety'
}
def _positive_spoken_number(n):
"""Assume n is a positive integer.
>>> _positive_spoken_number(900)
'nine hundred'
>>> _positive_spoken_number(100)
'one hundred'
>>> _positive_spoken_number(100000000000)
'one hundred billion'
>>> _positive_spoken_number(1000000000000)
'one trillion'
>>> _positive_spoken_number(33000000000000)
'thirty-three trillion'
>>> _positive_spoken_number(34954523539)
'thirty-four billion, nine hundred fifty-four million, five hundred twenty-three thousand, five hundred thirty-nine'
"""
#import sys; print >>sys.stderr, n
if n in _known:
return _known[n]
bestguess = str(n)
remainder = 0
if n<=20:
print >>sys.stderr, n, "How did this happen?"
assert 0
elif n < 100:
bestguess= _positive_spoken_number((n//10)*10) + '-' + \
_positive_spoken_number(n%10)
return bestguess
elif n < 1000:
bestguess= _positive_spoken_number(n//100) + ' ' + 'hundred'
remainder = n%100
elif n < 1000000:
bestguess= _positive_spoken_number(n//1000) + ' ' + 'thousand'
remainder = n%1000
elif n < 1000000000:
bestguess= _positive_spoken_number(n//1000000) + ' ' + 'million'
remainder = n%1000000
elif n < 1000000000000:
bestguess= _positive_spoken_number(n//1000000000) + ' ' + 'billion'
remainder = n%1000000000
else:
bestguess= _positive_spoken_number(n//1000000000000)+' '+'trillion'
remainder = n%1000000000000
if remainder:
if remainder >= 100: comma = ','
else: comma = ''
return bestguess + comma + ' ' + _positive_spoken_number(remainder)
else:
return bestguess
def spoken_number(n):
"""Return the number as it would be spoken, or just str(n) if unknown.
>>> spoken_number(0)
'zero'
>>> spoken_number(1)
'one'
>>> spoken_number(2)
'two'
>>> spoken_number(-2)
'minus two'
>>> spoken_number(42)
'forty-two'
>>> spoken_number(-1011)
'minus one thousand eleven'
>>> spoken_number(1111)
'one thousand, one hundred eleven'
"""
if not isinstance(n, int) and not isinstance(n, long): return n
if n<0:
if n in _known: return _known[n]
else: return 'minus ' + _positive_spoken_number(-n)
return _positive_spoken_number(n)
_aberrant_plurals = { 'knife' : 'knives',
'self' : 'selves',
'elf' : 'elves',
'life' : 'lives',
'hoof' : 'hooves',
'leaf' : 'leaves',
'echo' : 'echoes',
'embargo' : 'embargoes',
'hero' : 'heroes',
'potato' : 'potatoes',
'tomato' : 'tomatoes',
'torpedo' : 'torpedoes',
'veto' : 'vetoes',
'child' : 'children',
'woman' : 'women',
'man' : 'men',
'person' : 'people',
'goose' : 'geese',
'mouse' : 'mice',
'barracks' : 'barracks',
'deer' : 'deer',
'nucleus' : 'nuclei',
'syllabus' : 'syllabi',
'focus' : 'foci',
'fungus' : 'fungi',
'cactus' : 'cacti',
'phenomenon' : 'phenomena',
'index' : 'indices',
'appendix' : 'appendices',
'criterion' : 'criteria'
}
def how_many(n, singular, plural=None):
"""Return a string describing a number of thing or things
If plural is not supplied, it is guessed from singular.
Assume that all letters (except maybe the first) are lower case, for now.
@todo: Handle upper-case
>>> how_many(0, 'error')
'zero errors'
>>> how_many(1, 'error')
'one error'
>>> how_many(0, 'zero')
'zero zeroes'
>>> how_many(-99, 'penny')
'minus ninety-nine pennies'
>>> how_many(42, 'radius')
'forty-two radii'
>>> how_many(100, 'fuss')
'one hundred fusses'
>>> how_many(111, 'goose')
'one hundred eleven geese'
>>> how_many(-1, 'Chris', "Chris's")
"minus one Chris's"
"""
try: said = spoken_number(n)
except: said = str(n)
if n == 1:
return said + ' ' + singular
if plural: pass
elif singular in _aberrant_plurals: plural = _aberrant_plurals[singular]
else:
root = singular
post = ''
try:
vowels = 'aeiou'
if singular[-1] == 'y' and singular[-2] not in vowels:
root = singular[:-1]; post = 'ies'
elif singular[-1] == 's':
if singular[-2] in vowels:
if singular[-3:] == 'ius': root = singular[:-2]; post = 'i'
else: root = singular[:-1]; post = 'ses'
else: post = 'es'
elif singular[-1] in 'o' or singular[-2:] in ('ch', 'sh'):
post = 'es'
else:
post = 's'
except:
post = 's'
plural = root + post
return said + ' ' + plural
def _test():
import doctest
return doctest.testmod()
if __name__ == '__main__':
_test()
|
Making singular phrases plural is simple for English-speakers, but not quite so simple for computers. Combine that with long-hand numbers (the way you write checks) and suddenly you can impress your boss with very little effort.
The first solution that comes to mind is something like: <pre> def plural(n, word): return "%d %s%s" %(n, word, n==1?'':'s') </pre> Of course, Python does not have the ?: operator!
Once I bothered to write a function for plural, I decided to make it as general as possible. This does a pretty good job. The main things to notice are:
- I use '//' instead of '/', as the meaning of '/' will change in a future release of Python (2.4 I think)
- type long is not the same as type int, but they are treated similarly
If you prefer to remove the commas, to use 'negative' instead of 'minus', or to insert 'and' before the teens, those are simple changes. (A pedant would say that 'and' represents the decimal point in standard English, but use whatever sounds right to you.)
Enjoy!
See also: Recipe/82102 (Maybe I should have cut-and-pasted that code, since it is very similar. I did steal a table.)
For the Python ?: idiom, see Recipe/81058 or Recipe/52282 Basically, you could use this: <pre> return "%d %s%s" % (num, text, "s"[num==1:]) </pre>
Ternary operator in Python. Python may not have C's ternary operator (i.e. x ? a : b meaning a if x is true else b), but you can achieve the same effect with x and a or b:
The one limitation is that a must be a True value; otherwise you will always get b. You can work around this by inverting the condition:
Bigger problems. This might work great for english only, but is horrid for other languages (where there might be one form for a single object, another for two and a third for everything up). For that you would probably want to use the ngettext function in the gettext module, (or atleast write up a small function like it)
A Lazy Way of Handling Plurals.
Maybe trivial, but at least hard to screw up. :)
Slightly less trivial. I had just noticed the previous comment, so a version that operates identically for simple singulars and plurals, but lets you specify different terms for specific counts of items:
Nits. The if/elif chain in _positive_spoken_number() could be reversed so that the more common cases are listed first.
Given transformations of numbers and pluralization of nouns, consider providing accompanying transformations for subject/verb agreement: "there is one goose" vs "there are six geese".
Good point. Reversed elifs, done and tested.
Verb and noun cases would be sweet! But I use this only for reporting statistics at the end of a program.
Of course, this recipe has very limited value, but the coolness factor is considerable if you use it in a program that someone else sees. Regular people say, "Wow. Cool."
For a comprehensive plural and numbers-to-words generator see inflect.py http://pypi.python.org/pypi/inflect