I am currently looking for the way to replace words like first, second, third,...with appropriate ordinal number representation (1st, 2nd, 3rd). I have been googling for the last week and I didn't find any useful standard tool or any function from NLTK.
So is there any or should I write some regular expressions manually?
Thanks for any advice
Here's a terse solution taken from Gareth on codegolf:
ordinal = lambda n: "%d%s" % (n,"tsnrhtdd"[(n/10%10!=1)*(n%10<4)*n%10::4])
Works on any number:
print([ordinal(n) for n in range(1,32)])
['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th',
'11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th',
'20th', '21st', '22nd', '23rd', '24th', '25th', '26th', '27th', '28th',
'29th', '30th', '31st']
For python 3.4+, math.floor
is needed:
import math
ordinal = lambda n: "%d%s" % (n,"tsnrhtdd"[(math.floor(n/10)%10!=1)*(n%10<4)*n%10::4])
How about this:
suf = lambda n: "%d%s"%(n,{1:"st",2:"nd",3:"rd"}.get(n if n<20 else n%10,"th"))
print [suf(n) for n in xrange(1,32)]
['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th',
'11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th',
'20th', '21st', '22nd', '23rd', '24th', '25th', '26th', '27th', '28th',
'29th', '30th', '31st']
The accepted answer to a previous question has an algorithm for half of this: it turns "first"
into 1
. To go from there to "1st"
, do something like:
suffixes = ["th", "st", "nd", "rd", ] + ["th"] * 16
suffixed_num = str(num) + suffixes[num % 100]
This only works for numbers 0-19.
I wanted to use ordinals for a project of mine and after a few prototypes I think this method although not small will work for any positive integer, yes any integer.
It works by determiniting if the number is above or below 20, if the number is below 20 it will turn the int 1 into the string 1st , 2 , 2nd; 3, 3rd; and the rest will have "st" added to it.
For numbers over 20 it will take the last and second to last digits, which I have called the tens and unit respectively and test them to see what to add to the number.
This is in python by the way, so I'm not sure if other languages will be able to find the last or second to last digit on a string if they do it should translate pretty easily.
def o(numb):
if numb < 20: #determining suffix for < 20
if numb == 1:
suffix = 'st'
elif numb == 2:
suffix = 'nd'
elif numb == 3:
suffix = 'rd'
else:
suffix = 'th'
else: #determining suffix for > 20
tens = str(numb)
tens = tens[-2]
unit = str(numb)
unit = unit[-1]
if tens == "1":
suffix = "th"
else:
if unit == "1":
suffix = 'st'
elif unit == "2":
suffix = 'nd'
elif unit == "3":
suffix = 'rd'
else:
suffix = 'th'
return str(numb)+ suffix
I called the function "o" for ease of use and can be called by importing the file name which I called "ordinal" by import ordinal then ordinal.o(number).
Let me know what you think :D
I found myself doing something similar, needing to convert addresses with ordinal numbers ('Third St') to a format that a geocoder could comprehend ('3rd St'). While this isn't very elegant, one quick and dirty solution is to use the inflect.py to generate a dictionary for translation.
inflect.py has a number_to_words()
function, that will turn a number (e.g. 2
) to it's word form (e.g. 'two'
). Additionally, there is an ordinal()
function that will take any number (numeral or word form) and turn it into it's ordinal form (e.g. 4
-> fourth
, six
-> sixth
). Neither of those, on their own, do what you're looking for, but together you can use them to generate a dictionary to translate any supplied ordinal-number-word (within a reasonable range) to it's respective numeral ordinal. Take a look:
>>> import inflect
>>> p = inflect.engine()
>>> word_to_number_mapping = {}
>>>
>>> for i in range(1, 100):
... word_form = p.number_to_words(i) # 1 -> 'one'
... ordinal_word = p.ordinal(word_form) # 'one' -> 'first'
... ordinal_number = p.ordinal(i) # 1 -> '1st'
... word_to_number_mapping[ordinal_word] = ordinal_number # 'first': '1st'
...
>>> print word_to_number_mapping['sixth']
6th
>>> print word_to_number_mapping['eleventh']
11th
>>> print word_to_number_mapping['forty-third']
43rd
If you're willing to commit some time, it might be possible to examine inflect.py's inner-workings in both of those functions and build your own code to do this dynamically (I haven't tried to do this).
Another solution is the num2words
library (pip | github).
It especially offers different languages, so localization/internationalization (aka. l10n/i18n) is a no-brainer.
Usage is easy after you installed it with pip install num2words
:
from num2words import num2words
# english is default
num2words(4458, to="ordinal_num")
'4458rd'
# examples for other languages
num2words(4458, lang="en", to="ordinal_num")
'4458rd'
num2words(4458, lang="es", to="ordinal_num")
'4458º'
num2words(4458, lang="de", to="ordinal_num")
'4458.'
num2words(4458, lang="id", to="ordinal_num")
'ke-4458'
Bonus:
num2words(4458, lang="en", to="ordinal")
'four thousand, four hundred and fifty-eighth'
If you don't want to pull in an additional dependency on an external library (as suggested by luckydonald) but also don't want the future maintainer of the code to haunt you down and kill you (because you used golfed code in production) then here's a short-but-maintainable variant:
def make_ordinal(n):
'''
Convert an integer into its ordinal representation::
make_ordinal(0) => '0th'
make_ordinal(3) => '3rd'
make_ordinal(122) => '122nd'
make_ordinal(213) => '213th'
'''
n = int(n)
suffix = ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]
if 11 <= (n % 100) <= 13:
suffix = 'th'
return str(n) + suffix
If using django, you could do:
from django.contrib.humanize.templatetags.humanize import ordinal
var = ordinal(number)
(or use ordinal in a django template as the template filter it was intended to be, though calling it like this from python code works as well)
If not using django you could steal their implementation which is very neat.
this function works well for each number n. If n is negative, it is converted to positive. If n is not integer, it is converted to integer.
def ordinal( n ):
suffix = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']
if n < 0:
n *= -1
n = int(n)
if n % 100 in (11,12,13):
s = 'th'
else:
s = suffix[n % 10]
return str(n) + s
If you don't want to import an external module and prefer a one-line solution, then the following is probably (slightly) more readable than the accepted answer:
def suffix(i):
return {1:"st", 2:"nd", 3:"rd"}.get(i%10*(i%100 not in [11,12,13]), "th"))
It uses dictionary .get
, as suggested by https://codereview.stackexchange.com/a/41300/90593 and https://stackoverflow.com/a/36977549/5069869.
I made use of multiplication with a boolean to handle the special cases (11,12,13) without having to start an if-block. If the condition (i%100 not in [11,12,13])
evaluates to False
, the whole number is 0 and we get the default 'th' case.
This can handle any length number, the exceptions for ...#11 to ...#13 and negative integers.
def ith(i):return(('th'*(10<(abs(i)%100)<14))+['st','nd','rd',*['th']*7][(abs(i)-1)%10])[0:2]
I suggest using ith() as a name to avoid overriding the builtin ord().
# test routine
for i in range(-200,200):
print(i,ith(i))
Note: Tested with Python 3.6; The abs() function was available without explicitly including a math module.
This is an alternative options using num2words package.
>>> from num2words import num2words
>>> num2words(42, to='ordinal_num')
'42nd'
Try this
import sys
a = int(sys.argv[1])
for i in range(1,a+1):
j = i
if(j%100 == 11 or j%100 == 12 or j%100 == 13):
print("%dth Hello"%(j))
continue
i %= 10
if ((j%10 == 1) and ((i%10 != 0) or (i%10 != 1))):
print("%dst Hello"%(j))
elif ((j%10 == 2) and ((i%10 != 0) or (i%10 != 1))):
print("%dnd Hello"%(j))
elif ((j%10 == 3) and ((i%10 != 0) or (i%10 != 1))):
print("%drd Hello"%(j))
else:
print("%dth Hello"%(j))
Here is a more complicated solution I just wrote that takes into account compounded ordinals. So it works from first
all the way to nine hundred and ninety ninth
. I needed it to convert string street names to the number ordinals:
import re
from collections import OrderedDict
ONETHS = {
'first': '1ST', 'second': '2ND', 'third': '3RD', 'fourth': '4TH', 'fifth': '5TH', 'sixth': '6TH', 'seventh': '7TH',
'eighth': '8TH', 'ninth': '9TH'
}
TEENTHS = {
'tenth': '10TH', 'eleventh': '11TH', 'twelfth': '12TH', 'thirteenth': '13TH',
'fourteenth': '14TH', 'fifteenth': '15TH', 'sixteenth': '16TH', 'seventeenth': '17TH', 'eighteenth': '18TH',
'nineteenth': '19TH'
}
TENTHS = {
'twentieth': '20TH', 'thirtieth': '30TH', 'fortieth': '40TH', 'fiftieth': '50TH', 'sixtieth': '60TH',
'seventieth': '70TH', 'eightieth': '80TH', 'ninetieth': '90TH',
}
HUNDREDTH = {'hundredth': '100TH'} # HUNDREDTH not s
ONES = {'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5', 'six': '6', 'seven': '7', 'eight': '8',
'nine': '9'}
TENS = {'twenty': '20', 'thirty': '30', 'forty': '40', 'fifty': '50', 'sixty': '60', 'seventy': '70', 'eighty': '80',
'ninety': '90'}
HUNDRED = {'hundred': '100'}
# Used below for ALL_ORDINALS
ALL_THS = {}
ALL_THS.update(ONETHS)
ALL_THS.update(TEENTHS)
ALL_THS.update(TENTHS)
ALL_THS.update(HUNDREDTH)
ALL_ORDINALS = OrderedDict()
ALL_ORDINALS.update(ALL_THS)
ALL_ORDINALS.update(TENS)
ALL_ORDINALS.update(HUNDRED)
ALL_ORDINALS.update(ONES)
def split_ordinal_word(word):
ordinals = []
if not word:
return ordinals
for key, value in ALL_ORDINALS.items():
if word.startswith(key):
ordinals.append(key)
ordinals += split_ordinal_word(word[len(key):])
break
return ordinals
def get_ordinals(s):
ordinals, start, end = [], [], []
s = s.strip().replace('-', ' ').replace('and', '').lower()
s = re.sub(' +',' ', s) # Replace multiple spaces with a single space
s = s.split(' ')
for word in s:
found_ordinals = split_ordinal_word(word)
if found_ordinals:
ordinals += found_ordinals
else: # else if word, for covering blanks
if ordinals: # Already have some ordinals
end.append(word)
else:
start.append(word)
return start, ordinals, end
def detect_ordinal_pattern(ordinals):
ordinal_length = len(ordinals)
ordinal_string = '' # ' '.join(ordinals)
if ordinal_length == 1:
ordinal_string = ALL_ORDINALS[ordinals[0]]
elif ordinal_length == 2:
if ordinals[0] in ONES.keys() and ordinals[1] in HUNDREDTH.keys():
ordinal_string = ONES[ordinals[0]] + '00TH'
elif ordinals[0] in HUNDRED.keys() and ordinals[1] in ONETHS.keys():
ordinal_string = HUNDRED[ordinals[0]][:-1] + ONETHS[ordinals[1]]
elif ordinals[0] in TENS.keys() and ordinals[1] in ONETHS.keys():
ordinal_string = TENS[ordinals[0]][0] + ONETHS[ordinals[1]]
elif ordinal_length == 3:
if ordinals[0] in HUNDRED.keys() and ordinals[1] in TENS.keys() and ordinals[2] in ONETHS.keys():
ordinal_string = HUNDRED[ordinals[0]][0] + TENS[ordinals[1]][0] + ONETHS[ordinals[2]]
elif ordinals[0] in ONES.keys() and ordinals[1] in HUNDRED.keys() and ordinals[2] in ALL_THS.keys():
ordinal_string = ONES[ordinals[0]] + ALL_THS[ordinals[2]]
elif ordinal_length == 4:
if ordinals[0] in ONES.keys() and ordinals[1] in HUNDRED.keys() and ordinals[2] in TENS.keys() and \
ordinals[3] in ONETHS.keys():
ordinal_string = ONES[ordinals[0]] + TENS[ordinals[2]][0] + ONETHS[ordinals[3]]
return ordinal_string
And here is some sample usage:
# s = '32 one hundred and forty-third st toronto, on'
#s = '32 forty-third st toronto, on'
#s = '32 one-hundredth st toronto, on'
#s = '32 hundred and third st toronto, on'
#s = '32 hundred and thirty first st toronto, on'
# s = '32 nine hundred and twenty third st toronto, on'
#s = '32 nine hundred and ninety ninth st toronto, on'
s = '32 sixty sixth toronto, on'
st, ords, en = get_ordinals(s)
print st, detect_ordinal_pattern(ords), en
I salute Gareth's lambda code. So elegant. I only half-understand how it works though. So I tried to deconstruct it and came up with this:
def ordinal(integer):
int_to_string = str(integer)
if int_to_string == '1' or int_to_string == '-1':
print int_to_string+'st'
return int_to_string+'st';
elif int_to_string == '2' or int_to_string == '-2':
print int_to_string+'nd'
return int_to_string+'nd';
elif int_to_string == '3' or int_to_string == '-3':
print int_to_string+'rd'
return int_to_string+'rd';
elif int_to_string[-1] == '1' and int_to_string[-2] != '1':
print int_to_string+'st'
return int_to_string+'st';
elif int_to_string[-1] == '2' and int_to_string[-2] != '1':
print int_to_string+'nd'
return int_to_string+'nd';
elif int_to_string[-1] == '3' and int_to_string[-2] != '1':
print int_to_string+'rd'
return int_to_string+'rd';
else:
print int_to_string+'th'
return int_to_string+'th';
>>> print [ordinal(n) for n in range(1,25)]
1st
2nd
3rd
4th
5th
6th
7th
8th
9th
10th
11th
12th
13th
14th
15th
16th
17th
18th
19th
20th
21st
22nd
23rd
24th
['1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th',
'11th', '12th', '13th', '14th', '15th', '16th', '17th', '18th', '19th',
'20th', '21st', '22nd', '23rd', '24th']
Gareth's code expressed using the modern .format()
ordinal = lambda n: "{}{}".format(n,"tsnrhtdd"[(n/10%10!=1)*(n%10<4)*n%10::4])
There's an ordinal function in humanize
pip install humanize
>>> [(x, humanize.ordinal(x)) for x in (1, 2, 3, 4, 20, 21, 22, 23, 24, 100, 101,
... 102, 103, 113, -1, 0, 1.2, 13.6)]
[(1, '1st'), (2, '2nd'), (3, '3rd'), (4, '4th'), (20, '20th'), (21, '21st'),
(22, '22nd'), (23, '23rd'), (24, '24th'), (100, '100th'), (101, '101st'),
(102, '102nd'), (103, '103rd'), (113, '113th'), (-1, '-1th'), (0, '0th'),
(1.2, '1st'), (13.6, '13th')]
来源:https://stackoverflow.com/questions/9647202/ordinal-numbers-replacement