Find ordinal numbers with loop dynamically: find th - st - nd - rd

Viewed 609

I would like to find dynamically the correct ordinal number root for instance:

111 = 111st
112 = 112nd 
113 = 113rd ...

I tried other solutions but I can't find a good one.

This is my code:

for number in range(1, 114):
    print(number)
    ex1 = 11
    ex2 = 12
    ex3 = 13
    if number == ex1:
        print("is the " + str(number) + "th number.")
    elif number % 10 == 1 or not ex1:
        print("is the " + str(number) + "st number.")
    elif number == ex2:
        print("is the " + str(number) + "nd number.")
    elif number % 10 == 2 or not ex2:
        print("is the " + str(number) + "nd number.")
    elif number == ex3:
        print("is the " + str(number) + "rd number.")
    elif number % 10 == 3 or not ex3:
        print("is the " + str(number) + "rd number")
    else:
        print("is the " + str(number) + "th number.")
4 Answers

Note that 11, 12 and 13 have th suffix.
Also note you can change the end of the line in print function (default \n):

print('text', end=' ')
print('another text')

Then, I suggest you to use formatted string using f"{data} constant text" or "{} constant text".format(data).

Here is my solution to your problem:

def getSuffix(n):
    if n < 0: raise Exception("Ordinal negative numbers are not allowed")
    if n % 100 in [11, 12, 13]: return 'th'
    if n % 10 == 1: return 'st'
    if n % 10 == 2: return 'nd'
    if n % 10 == 3: return 'rd'
    return 'th'


for number in range(1, 114):
    print(f"{number} is the {number}{getSuffix(number)} number")

I hope I was helpful.

This is a pretty good solution:

ordinal = lambda n: "%d%s" % (n, "tsnrhtdd"[(n // 10 % 10 != 1) * (n % 10 < 4) * n % 10::4])


for number in range(1, 114):
    print(f'the {ordinal(number)} number. :) ')

EDIT For Human Beings

NOTE: Variables name aren't meant to be used in a production environment, I tried to make it more explicit what each step on the lambda function does!

def get_ordinal(n):

    hacking_string = "tsnrhtdd"                                # 1)
    is_num_divisible_by_ten = (n // 10 % 10 != 1)              # 2)         
    is_num_reminder_0_3= (n % 10 < 4)                          # 3)
    are_both_false_or_both_true= is_num_divisible_by_ten * is_num_between_0_3  # 4)
    get_index = are_both_false_or_both_true* n % 10  # 5)
    
    return f'{number}{hacking_string[get_index::4]}'  #6)  ---> starts at t | s | n | r
    
for number in range(1, 114):
    print(f'the {get_ordinal(number)} number. :) ')

Considerations

The solution found is very Hacky and smart and I probably would never come up my self, is using some clever math tricks to find the off sets of the number. As requested I however simplified the function and added some explanation to it.

  • Step 1. This string is better seen it as this "tsnr" "htdd" | On the left side you heve the "root" of the string, on the right the end. (more explanation below)

  • Step 2. is_num_divisible_by_ten --> using a floor division the result is True or False.

  • Step 3. is_num_reminder_0_3 If checking if the reminder of N and 10 is between 0 & 3, returns a True / False Flag.

  • Step 4. are_both_false_or_both_true is multiplying 2 bool value, in Python True is a 1 and False is a 0, so is like do --> 1 * 0. The Variable is True only if both values are True or both are False, otherwise is always False.

  • Step 5. get_index - > Returns either 0 or 1 or 2 or 3.

  • Step 6. Here the hacky part, with the received index from get_index, is laverage the hacking_string variable with indexing-and-slicing:

The get_index value is always one of these: "tsnr" and the steps taken (4) any of these "rhtdd" hence the possible combination are:

get_index = 0 = "th"  
get_index = 1 = "st"
get_index = 2 = "nd"
get_index = 3 = "rd"

Finally

The exact mathematics that goes behind it may be better asked on math.stackexchange or if someone knows it would be good to either add a comment or edit my answer!

References (It wasn't my solution)

Guides

So, the problem is that 111 gets displayed as 111st instead of 111th.

You have 11 as ex1, I assume short for "exception 1", but your condition:

if number == ex1:

Clearly doesn't match 111.

Instead you could do:

if number % 100 == ex1:

Which will be true for 11, 111, 211 etc.

On a side note:

elif number % 10 == 1 or not ex1:

Clearly isn't what you intended. This is interpreted as:

elif (number % 10 == 1) or (not ex1):

not ex1 does not depend on number and will always evaluate the same way (False). But since you're already checking ex1 separately, it would be redundant to do it correctly here.

If you wanted to correct that, so that you don't need to check ex1 twice, you'd do this:

if number % 10 == 1 and number % 100 != 11:

I think in this case using != is clearer than not and I don't think there is any benefit from assigning a variable to 11.

You can do it like that:

for number in range(1, 114):
    printedString = str(number)+' is the '+str(number)
    if str(number) == '1' or (str(number)[-1] == '1' and str(number)[-2] != '1'):
        printedString += 'st'
    elif str(number) == '2' or (str(number)[-1] == '2' and str(number)[-2] != '1'):
        printedString += 'nd'
    elif str(number) == '3' or (str(number)[-1] == '3' and str(number)[-2] != '1'):
        printedString += 'rd'
    else:
        printedString += 'th'
    print(printedString+' number.')
Related