# Range of ordinal numbers

My disgusting list comprehension in the return statement is quite a headful.

"""Hopefully this function will save you the trip to oocalc/excel.

"""

def rangeth(start, stop=None, skip=1):
"""rangeth([start,] stop[, skip])

returns a list of strings as places in a list (1st, 2nd, etc)

>>> rangeth(4)
['0th', '1st', '2nd', '3rd']

"""

if stop is None:
stop, start = start, 0

places = {'1':'st', '2':'nd', '3':'rd'}

return ["{}{}".format(i, places.get(i[-1], 'th')) \
if i[-2:] not in ['11', '12', '13'] else "{}{}".format(i, 'th') \
for i in map(str, range(start, stop, skip))]


Also, can someone explain to me how range accepts it's parameters? I have my ugly little boilerplate here that I wish didn't exist. I can't find the source for range, as I gave up after thinking it's probably some header file in include.

• Range - docs.python.org/library/functions.html#range. Which version of Python are you using? I'm a novice at python, but one thing I know is that yield (stackoverflow.com/questions/231767/…) would allow using loops which would most likely make this code much more readable (but maybe not as efficient, I don't know).
– Pat
Commented Feb 24, 2012 at 17:55
• This won't work version <= 2.6 due to the .format() function. Also, your link to the doc, though appreciated, isn't what I was after. If you look at the calltip in IDLE for range, there are no default values given. My function posts rangeth(start, stop=None..., which I don't like. Commented Feb 24, 2012 at 18:21
• Commented Feb 24, 2012 at 18:29
• I think your new version fails for 121. Commented Feb 25, 2012 at 3:36
• There is a module: inflect pypi.python.org/pypi/inflect/0.2.1 It has an ordinal-function which may be an alternate solution! ;-) Commented Mar 5, 2012 at 17:20

Firstly, I'd suggest not duplicating the overloading logic of range. Instead:

def rangeth(*args):
print range(*args)


Just take your arguments and pass them onto to range.

Secondly, I'd write a function that takes a single number and produces the "th"ified version.

def as_th(number):
number = str(number)
if number[-2:] in ['11','12','13]:
return "{}{}".format(number, 'th')
else:
return "{}{}".format(number, places.get(number[-1], 'th'))


I think that makes it easier to follow.

Then have you rangeth function be:

def rangeth(*args):
return map(as_th, range(*args))

• I didn't like returning the map object. I just used return [ nth(x) for x in range(*args) ]. But, +1 for producing the stand-alone nth(n) function, which is nice. Commented Feb 25, 2012 at 1:09
• Follow up: didn't realize that Python 3k returns map objects, which is why I wasn't getting a list back. Commented Feb 25, 2012 at 1:20

Adding my 2cents as a two-liner:

mark=lambda p:'{0}{1}'.format(p,('th','st','nd','rd')[p] if p%10<4 and p/10 != 1 else 'th')
map(mark, xrange(30))


delievers: ['0th', '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']

• Doesn't this miss the logic for numbers like "21st" Commented Feb 24, 2012 at 20:18
• This is obviously a function that could be reused and would be relevant in many situations, so turning it into a lambda is unnecessary and kills readability.
– Pat
Commented Feb 25, 2012 at 1:06
• A couple things...p can quickly climb out of the range of ('th', 'st', 'nd', 'rd') tuple. I fixed it, and will post it in my updated solution (I liked the fact that you never converted to str. So, net +1). Commented Feb 25, 2012 at 2:05