g | x | w | all
Bytes Lang Time Link
051Scala230729T105532Z138 Aspe
029PCRE Perlcompatible regular expression180424T092101ZToby Spe
037Factor + math.text.english220131T012526Zchunes
007Vyxal220131T010349Zlyxal
056GeoGebra220131T004522ZAiden Ch
065x86 machine code181104T150516ZModerate
060PHP181104T172741ZTitus
031Retina180414T175025ZEndenite
059Wolfram Language Mathematica180424T072454ZDELETE_M
068Python 2180414T230513ZOliver N
122Wolfram Language Mathematica180420T182527ZKelly Lo
063Excel180420T174021ZEngineer
051Java 8180416T075215ZKevin Cr
039Ruby180417T160143ZAsone Tu
022Jelly180415T141529ZJonathan
053Python180415T131154ZJonathan
100Haskell180416T183947ZWheat Wi
02405AB1E180416T113425ZEmigna
057Perl 5 n180416T033348ZXcali
070Python 2180414T212324ZChas Bro
060Pyth180414T212854Zhakr14
nan180414T172635ZDanielIn
054Bash + GNU utilities180414T165004ZDigital

Scala, 51 bytes

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

Try it online!

s=>s.matches(".*1.th|(.*[^1])?(1s|2n|3r|[^1-3]t).")

PCRE (Perl-compatible regular expression), 29 bytes

1.th|(?<!1)(1s|2n|3r)|[4-90]t

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

#!/usr/bin/bash

ok=(✓ ❌)

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

Results

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

Factor + math.text.english, 37 bytes

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

enter image description here

Explanation

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

Vyxal, 7 bytes

₌Ǎ⌊∆o$c

Try it Online!

Because we have a built-in for that.

Explained

₌Ǎ⌊∆o$c
₌Ǎ⌊     # 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? 

GeoGebra, 56 bytes

s="1nd"
b=Ordinal(FromBase(Take(s,1,Length(s)-2),10))==s

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.

Explanation:

Ordinal(FromBase(Take(s,1,Length(s)-2),10))==s
                 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?

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

Assembly:

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
	read_str:
		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

		tens:
		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)

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

Try it online!

PHP, 60 bytes

boring: regexp once more the shortest solution

<?=preg_match("/([^1]|^)(1st|2nd|3rd|\dth)$|1\dth$/",$argn);

empty output for falsy, 1 for truthy.

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

Retina, 35 31 bytes

-4 bytes thanks to @Asone Tuhid

Thanks to @Leo for finding a bug

1.th|(^|[^1])(1s|2n|3r|[04-9]t)

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!

Wolfram Language (Mathematica), 65 59 bytes

SpokenString@p[[#]]~StringTake~{5,-14}&@@ToExpression@#==#&

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.

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]
a='tsnr'+'t'*6

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

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.

StringMatchQ[((d=DigitCharacter)...~~"1"~(e=Except)~d~~(e["1"|"2"|"3",d]~~"th")|("1st"|"2nd"|"3rd"))|(d...~~"1"~~d~~"th")]

Try it online!

Excel, 63 bytes

=A1&MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2)

(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)

Java 8, 54 51 bytes

s->s.matches(".*1.th|(.*[^1])?(1s|2n|3r|[^1-3]t).")

Explanation:

Try it online.

s->  // Method with String parameter and boolean return-type
  s.matches(".*1.th|(.*[^1])?(1s|2n|3r|[^1-3]t).")
     //  Validates if the input matches this entire regex

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

Regex explanation:

^.*1.th|(.*[^1])?(1s|2n|3r|[^1-3]t).$
^                                          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

Ruby, 42 39 bytes

Lambda:

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

Try it online!

User input:

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

Try it online!

Matches:

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

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

Try it online!

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

Jelly,  25  22 bytes

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

ḣ-2VDṫ-’Ạ×ɗ/«4ị“snrh”e

A monadic link.

Try it online! Or see the test-suite.

How?

ḣ-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,  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!

How?

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'.

Haskell, 100 bytes

f('1':_:a@[_,_])=a=="th"
f(a:b)|length b>2=f b
f(a:"th")|a>'3'=1>0
f x=elem x$words"0th 1st 2nd 3rd"

Try it online!

05AB1E, 24 bytes

0ìþR2£`≠*.•’‘vê₅ù•sèsáнQ

Try it online! or as a Test suite

Explanation

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

Perl 5 -n, 57 bytes

/..$/;say$&eq(th,st,nd,rd,(th)x6)[$_%100-$_%10-10&&$_%10]

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!

Pyth, 49 60 bytesSBCS

Js<2zK%J100I||qK11qK12qK13q>2z"th".?qz+J@c."dt8¸*£tÎðÎs"2J

Test suite

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

Explanation:
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:
z=input();J=int(z[:-2]);K=J%100
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])

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

s=>("tsnr"[~~((n=(o=s.match(/(\d{1,2})(\D)/))[1])/10%10)-1?n%10:0]||'t')==o[2]

Try it online!

Explanation

s=>
   ("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

s=>/1.th|(^|[^1])(1st|2nd|3rd|[^1-3]th)/.test(s)

Try it online!

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!