Description :

Given a string as input, check if it is a valid ordinal number in English or not. If it is valid return truthy value otherwise return falsy value. (Suggested by @Arnauld. Thanks . Also by @JoKing)

For users who want to know about ordinal numbers go here :

https://www.mathsisfun.com/numbers/cardinal-ordinal-chart.html (Suggestion by : qwr)

Possible inputs :

21st ---> true
12nd ---> false
1nd ---> false

This is a code golf challenge so shortest code in each language will be the winner.

Examples :

console.log('12th' , true) // This evaluates to true
console.log('1st' , true) // also evaluates to true
console.log('21nd' , false) // returns false
console.log('11st' , false) // returns false
console.log('111199231923819238198231923213123909808th' , true) // true

Since a lot of people asked the question regarding whether input will be only valid strings or not :

All inputs will always be valid. i.e they will be in the form of string and consist of a digit (or number of digits) along with one of the four suffixes :

st , nd , rd , th

  • \$\begingroup\$ Can you clarify the rules of ordinal numbers? Or at least put a link to what the rules you are following. \$\endgroup\$
    – qwr
    Commented Apr 14, 2018 at 17:00
  • \$\begingroup\$ They are normal rules. I changed nothing. But thanks for the input , I added a link \$\endgroup\$ Commented Apr 14, 2018 at 17:02
  • \$\begingroup\$ @Jonathan Allan Ordinal numbers start from 1st, negative ordinals do not exist - english.stackexchange.com/questions/309713/… \$\endgroup\$
    – Oliver Ni
    Commented Apr 14, 2018 at 23:18
  • \$\begingroup\$ @JonathanAllan OP says "Input is going to be valid ordinal pattern." which means no negatives \$\endgroup\$
    – Oliver Ni
    Commented Apr 15, 2018 at 5:12
  • 3
    \$\begingroup\$ You say the inputs will always be valid but I think a better term would be well formed. Both 12th and 12nd are well formed but only the former is valid. \$\endgroup\$ Commented Apr 20, 2018 at 21:05

23 Answers 23


Python,  56  53 bytes

-3 thanks to (use unique letter inclusion instead of penultimate character equality)

lambda v:'hsnrhhhhhh'[(v[-4:-3]!='1')*int(v[-3])]in v

An unnamed function.

Try it online!


Since all input (here v) is guaranteed to be of the form \d*[st|nd|rd|th] we can just test whether a character exists in v which we expect to be there if it were correct (s, n, r, or h, respectively) - that is <getExpectedLetter>in v.

The last digit usually determines this:

v[-3]: 0 1 2 3 4 5 6 7 8 9
v[-2]: h s n r h h h h h h

...except when the penultimate digit is a 1, when all should end with th and hence our expected character must be h; to evaluate this we can take a slice (to avoid an index error occurring for inputs with no -4th character) v[-4:-3]. Since 0 maps to h already we can achieve the desired effect using multiplication prior to indexing into 'hsnrhhhhhh'.

  • \$\begingroup\$ st, nd, rd and th all have a unique letter so you can just test if it occurs in the string 53 bytes \$\endgroup\$ Commented Apr 17, 2018 at 16:14
  • \$\begingroup\$ @AsoneTuhid nice golf - thanks! \$\endgroup\$ Commented Apr 17, 2018 at 16:41
  • \$\begingroup\$ @AsoneTuhid - also saved three on my Jelly answer, so double the thanks! \$\endgroup\$ Commented Apr 17, 2018 at 16:47

Bash + GNU utilities, 54

Regex matching seems to be a straightforward way to go. I'm pretty sure this expression could be shortened more:

egrep '((^|[^1])(1st|2nd|3rd)|(1.|(^|[^1])[^1-3])th)$'

Input from STDIN. Output as a shell return code - 0 is truthy and 1 is falsey.

Try it online!

  • \$\begingroup\$ What ? this is not outputting the correct answer. \$\endgroup\$ Commented Apr 14, 2018 at 16:58
  • \$\begingroup\$ @MuhammadSalman That's because it's a test suite. Take a look at the exit codes for 1st and 1th. \$\endgroup\$
    – Dennis
    Commented Apr 14, 2018 at 17:09
  • \$\begingroup\$ egrep is capable of addition and primality testing (in unary), so I think you can make this an egrep answer. \$\endgroup\$
    – Dennis
    Commented Apr 14, 2018 at 17:10
  • \$\begingroup\$ I am sorry but my bash sucks as in I have no idea about a thing in it. I got bored so used a diff checker to check for difference between input and output. I see your point. So I have one question now @Dennis : Does Bash have booleans ? \$\endgroup\$ Commented Apr 14, 2018 at 17:13
  • \$\begingroup\$ Maybe the test cases would be more clear if egrep would be executed separately for each input to get matching exit code for each: Try it online!. \$\endgroup\$
    – manatwork
    Commented Apr 14, 2018 at 17:15

this is under the assumption that the input is valid ordinal pattern. if its not the case changes need to be made

JavaScript (Node.js),97 92 78 bytes


Try it online!


   ("tsnr"                                // all the options for ordinal - 4-9 will be dealt afterwards    
      [~~(                                //floor the result of the next expression
        (n=(                              //save the number (actually just the two right digits of it into n
          o=s.match(/(\d{1,2})(\D)/))[1]) //store the number(two digits) and the postfix into o (array)
        /10%10)-1                         //if the right most(the tenths digit) is not 1 (because one is always 'th')
          ?n%10:0]                        //return n%10 (where we said 0-3 is tsnr and afterwards is th
            ||'t')                        // if the result is undefined than the request number was between 4 and 9 therefor 'th' is required
    ==o[2]                                // match it to the actual postfix  


port of @Herman Lauenstein

JavaScript (Node.js), 48 bytes


Try it online!

  • \$\begingroup\$ If the assumption reg solution can also be ***. \$\endgroup\$
    – l4m2
    Commented Apr 14, 2018 at 20:12
  • \$\begingroup\$ If not assumed it's /\d*(st|nd|rd|th)/ input, 1sta pass the reg test; if assumed, /1.th|(^|[^1])(1s|2n|3r|[^1-3]t)/ work \$\endgroup\$
    – l4m2
    Commented Apr 14, 2018 at 23:29

Java 8, 54 51 bytes



Try it online.

s->  // Method with String parameter and boolean return-type
     //  Validates if the input matches this entire regex

Java's String#matches implicitly adds ^...$.

Regex explanation:

^                                          Start of the regex
 .*1.                                       If the number ends in 11-19:
     th                                      it must have a trailing th
       |                                    If not:
        (.*    )?                            Optionally it has leading digits,
           [^1]                              excluding a 1 at the end
                 (1s|2n|3r         .      followed by either 1st, 2nd, 3rd,
                          |[^1-3]t).      0th, 4th, 5th, ..., 8th, or 9th
                                    $   End of the regex

Pyth, 49 60 bytesSBCS


Test suite

SE ate some unprintables in the code (and in the below explanation) but they're present in the link.

Js<2zK%J100I||qK11qK12qK13q>2z"th".?qz+J@c."dt8¸*£tÎðÎs"2J # Code
Js<2z                                                         # J= the integer in the input
     K%J100                                                   # K=J%100
           I||qJ11qJ12qJ13                                    # IF K is 11, 12, or 13:
                          q>2z"th"                            #  Print whether the end of the input is "th"
                                  .?                          # Otherwise:
                                    qz                        #  Print whether the input is equal to
                                      +J                      #   J concatenated with
                                        @                   J #    The object at the Jth modular index of
                                          ."dt8¸*£tÎðÎs"   #     The string "thstndrdthththththth"
                                         c                 2  #      Chopped into strings of length 2 as a list
Python 3 translation:
if K==11or K==12or K==13:print(z[-2:]=="th")
else:print(z==str(J)+["thstndrdthththththth"[2*i:2*i+2] for i in range(10)][J%10])

Jelly,  25  22 bytes

-3 bytes thanks to an observation made in a comment made by on my Python entry.


A monadic link.

Try it online! Or see the test-suite.


ḣ-2VDṫ-’Ạ×ɗ/«4ị“snrh”e - Link: list of characters   e.g. "213rd" or "502nd" or "7th"
ḣ-2                    - head to index -2                "213"      "502"      "7"
   V                   - evaluate                         213        502        7
    D                  - cast to decimal list            [2,1,3]    [5,0,2]    [7]
     ṫ-                - tail from index -1                [1,3]      [0,2]    [7]
           /           - reduce with:                                          (no reduction since already length 1)
          ɗ            -   last 3 links as a dyad:                           
       ’               -     decrement (the left)           0         -1        x
        Ạ              -     all? (0 if 0, 1 otherwise)     0          1        x
         ×             -     multiply (by the right)        0          2        x
            «4         - minimum of that and 4              0          2        4
              ị“snrh”  - index into "snrh"                 'h'        'n'      'h'
                     e - exists in? (the input list)        0          1        1

Python 2, 92 82 74 68 bytes

-8 thanks to Chas Brown
-6 thanks to Kevin Cruijssen

lambda s:(a+'t'*10+a*8)[int(s[-4:-2]):][:1]==s[-2:-1]

Constructs a big string of ths, sts, nds, and rds for endings 00 to 99. Then checks to see if it matches.


Retina, 35 31 bytes

-4 bytes thanks to @Asone Tuhid

Thanks to @Leo for finding a bug


Outputs 1 for true and 0 for false. This assumes the input is in ordinal format with a valid suffix (ends with st, nd, rd or th).

Try it online!


Perl 5 -n, 57 bytes


Try it online!


Haskell, 100 bytes

f(a:b)|length b>2=f b
f x=elem x$words"0th 1st 2nd 3rd"

Try it online!


GeoGebra, 56 bytes


Try It On GeoGebra!

Input is s, Output is b. b is true if s is a valid ordinal, false otherwise.

I just realized that GeoGebra has string support, so I had to try it.


                 Take(s,1,Length(s)-2)                s without the last two characters
        FromBase(                     ,10)            Convert the string to a number
Ordinal(                                  )           Convert the number to ordinal form
                                           ==s        Is it equal to s?

Vyxal, 7 bytes


Try it Online!

Because we have a built-in for that.


₌Ǎ⌊     # Push the letters and numbers of the input
∆o      # Push the corresponding ordinal string
$c      # are the letters of the string contained in the ordinal string? 

Factor + math.text.english, 37 bytes

[ 2 cut* swap dec> ordinal-suffix = ]

enter image description here


                ! "121th"
2 cut*          ! "121" "th"
swap            ! "th" "121"
dec>            ! "th" 121
ordinal-suffix  ! "th" "st"
=               ! f

Scala, 51 bytes

Port of @Kevin Cruijssen's Java answer in Scala.

Try it online!


Python 2, 94 82 77 73 70 bytes

lambda s:'tsnrthtddh'[min(4,int(s[-3])*(('0'+s)[-4]!='1'))::5]==s[-2:]

Try it online!


05AB1E, 24 bytes


Try it online! or as a Test suite


0ì                         # prepend 0 to input
  þ                        # remove letters
   R                       # reverse
    2£                     # take the first 2 digits
      `≠                   # check if the 2nd digit is false
        *                  # and multiply with the 1st digit
         .•’‘vê₅ù•         # push the string "tsnrtttttt"
                  sè       # index into this string with the number calculated
                    sáн    # get the first letter of the input
                       Q   # compare for equality

Ruby, 42 39 bytes



Try it online!

User input:

p gets*2=~/1..h|[^1](1s|2n|3r|[4-90]t)/

Try it online!


  • 1(anything)(anything)h - 12th
  • (not 1)1s - (1st)
  • (not 1)2n - (2nd)
  • (not 1)3r - (3rd)

Because [^1] (not 1) doesn't match the beginning of a string, the input is duplicated to make sure there's a character before the last.

Ruby -n, 35 bytes


Try it online!

Same idea as above but instead of duplicating the string, this also matches the start of the string (^).


Excel, 63 bytes


(MOD(A1-11,100)>2) returns FALSE when A1 ends with 11-13

2*RIGHT(A1)*(MOD(A1-11,100)>2)+1 returns 1 if it's in 11-13 and 3,5,7,etc. otherwise

MIN(9,~) changes any returns above 9 into 9 to pull the th from the string

MID("thstndrdth",MIN(~),2) pulls out the first th for inputs ending in 11-13, st for 1, nd for 2, rd for 3, and the last th for anything higher.

=A1&MID(~) prepends the original number to the ordinal.

Posting as wiki since I am not the author of this. (Source)


Wolfram Language (Mathematica), 122 bytes

Unlike most of the other answers on here, this will actually return false when the input is not a "valid ordinal pattern", so it will correctly return false on input like "3a23rd", "monkey" or "╚§+!". So I think this works for the entire set of possible input strings.


Try it online!


Wolfram Language (Mathematica), 65 59 bytes


Try it online!

Of course Mathematica has a built-in (although undocumented) for converting to ordinal number. Source.

(for the 65-byte version: according to that it seems that v9 and before doesn't need calling Speak before so it may be possible to save some more bytes)

Also check out KellyLowder's answer for a non-builtin version.


PHP, 60 bytes

boring: regexp once more the shortest solution


empty output for falsy, 1 for truthy.

Run as pipe with -nF or try it online. (TiO wrapped as function for convenience)


x86 machine code, 65 bytes

00000000: 31c0 4180 3930 7cfa 8079 0161 7ef4 8079  1.A.90|..y.a~..y
00000010: ff31 7418 31db 8a19 83eb 308a 9300 0000  .1t.1.....0.....
00000020: 0031 db43 3851 010f 44c3 eb0a 31db 4380  .1.C8Q..D...1.C.
00000030: 7901 740f 44c3 c374 736e 7274 7474 7474  y.t.D..tsnrttttt
00000040: 74                                       t


section .text
	global func
func:					;the function uses fastcall conventions
					;ecx=first arg to function (ptr to input string)
	xor eax, eax			;reset eax to 0
		inc ecx			;increment ptr to string

		cmp byte [ecx], '0'
		jl read_str		;if the char isn't a digit, get next digit
		cmp byte [ecx+1], 'a'
		jle read_str		;if the char after the digit isn't a letter, get next digit
		cmp byte [ecx-1], '1'
		je tens 		;10-19 have different rules, so jump to 'tens'
		xor ebx, ebx		;reset ebx to 0
		mov bl, byte [ecx]  	;get current digit and store in bl (low byte of ebx)
		sub ebx, 0x30		;convert ascii digit to number
		mov dl, [lookup_table+ebx] ;get correct ordinal from lookup table
		xor ebx, ebx		;reset ebx to 0
		inc ebx			;set ebx to 1
		cmp byte [ecx+1], dl	;is the ordinal correct according to the lookup table?
		cmove eax, ebx		;if the ordinal is valid, set eax (return reg) to 1 (in ebx)
		jmp end			;jump to the end of the function and return

		xor ebx, ebx		;reset ebx to 0
		inc ebx			;set ebx to 1
		cmp byte [ecx+1], 't'	;does it end in th?
		cmove eax, ebx		;if the ordinal is valid, set eax (return reg) to 1 (in ebx)

	ret				;return the value in eax
section .data
	lookup_table db 'tsnrtttttt'

Try it online!


PCRE (Perl-compatible regular expression), 29 bytes


We accept th after any "teen" number, or after any digit other than 1..3. For 1..3, we use a negative lookbehind to accept st, nd, or rd only when not preceded by 1.

Test program


ok=(✓ ❌)

for i
do grep -Pq '1.th|(?<!1)(1s|2n|3r)|[4-90]t' <<<"$i"; echo $i ${ok[$?]}


1st ✓
1th ❌
2nd ✓
2th ❌
3rd ✓
3th ❌
4st ❌
4th ✓
11th ✓
11st ❌
12nd ❌
12th ✓
13th ✓
13rd ❌
112nd ❌
112th ✓
21nd ❌
32nd ✓
33rd ✓
21th ❌
21st ✓
11st ❌
111199231923819238198231923213123909808th ✓

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.