[Question]
  [
# 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`
  
[Answer]
# [Python](https://docs.python.org/3/), ~~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!](https://tio.run/##RY9hC4MgEIY/b7/CfTJHgec1yKBf0vrQaGGwWZgE@/UuUzdBH9/j7r275WPVrNGNzd29@vdj6MlWU7Vqo45D22xri7IusLs0FCi7Ttr6EHasmzTZ3DgbYp@rJbvKKKyW5oQKPXigOVBa5XELkAHAIyFSBIpYCzx9fhkpBQCj1T8CIKVA2G8VnypIAQgCJZcVj@NA8I2lGFQZBoe0QDIWvjmrz6fF@M39pjkZDzLmvg "Python 3 – 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'`.
[Answer]
# [Bash + GNU utilities](https://www.gnu.org/software/bash/), 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!](https://tio.run/##NcpBCsIwEIXhfU7hQmiyUPJmFJKzSAtKi1mJtFnm7nFe1cD8MJPvcd9K78tzXd6Hwfup3SaMwWOrTV5z03UOzePc/j@Wk46hlnAcejfmjDlj7lKLu9ogMmCEUYvwJrsmF3oKAt0lr7CACPZyFoVN@iV9V4FCNMecYqrlAw "Bash – Try It Online")
[Answer]
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)](https://nodejs.org),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!](https://tio.run/##dY5PC4JAEMXvfYoIwhlI3Vkva7B16VuoB/FPFrYb7tYl86uboodIejDzGH684V3TZ2qy5nK3rtJ50ZeyN/IAG2tUs4m6DkBJ0NJ4t9RmFfgQ5y/a8TdCfEIfMaIEfWJbYujSUQ2@Z0nbOtZBKXXEkz7Tyui68Gp9hhIc4rYamW0eBa5Xv9TY/5CTykdaprUpcBGdsxNdfh4UhjygYcS8xHRyCogHIQsFE1/l@g8 "JavaScript (Node.js) – 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)](https://nodejs.org), 48 bytes
```
s=>/1.th|(^|[^1])(1st|2nd|3rd|[^1-3]th)/.test(s)
```
[Try it online!](https://tio.run/##dY69CsMwDIT3PkW32EN@ZC/24L5ISSDETtMS7GKpnfzubkIylIYeSHD6OHGP/t3jEO9PKn2wLo8mo7nUUNGUWJeuHbScAVIS3iYZ7XopZUsTrytySAx5HoLHMLtqDjc2sgIETQU3huLL8fPplyL9hwK8XenYz@j4IbpnN3r8vEhrIWEZtS@1WQEShNSNVo36Kpc/ "JavaScript (Node.js) – Try It Online")
[Answer]
# Java 8, ~~54~~ 51 bytes
```
s->s.matches(".*1.th|(.*[^1])?(1s|2n|3r|[^1-3]t).")
```
**Explanation:**
[Try it online.](https://tio.run/##jZQxb4MwEIX3/IoTE44UhzOdGrXdujVLukWp5BBSSMFE@IgUFX47JTRSp4q3WDr50/np3fOd7MUuToevPims9/Rmc/c9I8qdpPXRJimtbyXRvqqK1DpKwo3Uufskr1bDRTcbDi9W8oTW5OiJer949rq0kmSpDwM9Zy1ZG@r59oN36iVk3xrXxnU71It4J0oHql/d2pybfTG0uXe7VPmBykHO/cHtjqz61bJc0nvdSHZ9HMvN1Uta6qoRfR5IKVzodBIG7CVQo8r/GeMOk0xcTzMPkk0yHCEQI5BBoBiBEOEGEW4guxG/DWK4wRyHLI8Q6Rwh2jlCxHMEqQcDAyUGiwyWGSg0DKWGodgwlBuGgsPguCd9VePSGrfRqy18OrWNkD8EMNiEIFMRTzFLMUehBxkdIvY3/pR1s67/AQ)
```
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](https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#matches(java.lang.String)) 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
```
[Answer]
# Pyth, ~~49~~ 60 bytesSBCS
```
Js<2zK%J100I||qK11qK12qK13q>2z"th".?qz+J@c."dt8¸*£tÎðÎs"2J
```
[**Test suite**](https://pyth.herokuapp.com/?code=Js%3C2zK%25J100I%7C%7CqK11qK12qK13q%3E2z%22th%22.%3Fqz%2BJ%40c.%22dt%038%C2%B8%2a%C2%A3t%C3%8E%C2%98%C3%B0%C3%8Es%222J&test_suite=1&test_suite_input=21st%0A12nd%0A1nd%0A12th%0A1st%0A21nd%0A11st%0A111199231923819238198231923213123909808th%0A113th&debug=0)
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])
```
[Answer]
# [Jelly](https://github.com/DennisMitchell/jelly), ~~25~~ 22 [bytes](https://github.com/DennisMitchell/jelly/wiki/Code-page)
-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!](https://tio.run/##ATYAyf9qZWxsef//4bijLTJWROG5qy3igJnhuqDDl8mXL8KrNOG7i@KAnHNucmjigJ1l////MTAybmQ "Jelly – Try It Online")** Or see the [test-suite](https://tio.run/##y0rNyan8///hjsW6RmEuD3eu1n3UMPPhrgWHp5@crn9otcnD3d2PGuYU5xVlPGqYm/r/6J7D7Y@a1ngDceT//9HqhsUl6joK6kZ5KSDKuAhMmZRkgChTCGUJoQwNoLQhlDaC0sYQ2ghqhqEBjAFXCVcKU2uJEDE0tLQ0MjYEYgsoYQHhGhkaGxoZWxpYWhhAnWUIMReq1RjCM4F4wBDmEZjBRiDLYwE "Jelly – Try It Online").
### 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
```
[Answer]
# 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 `th`s, `st`s, `nd`s, and `rd`s for endings `00` to `99`. Then checks to see if it matches.
[Answer]
# [Retina](https://github.com/m-ender/retina/wiki/The-Language), ~~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!](https://tio.run/##K0otycxLNPz/31CvJKNGI64mOs4wVlPDsLjGKK/GuAjE1TWOLdH8/9/UrCQDAA "Retina – Try It Online")
[Answer]
# [Perl 5](https://www.perl.org/) `-n`, 57 bytes
```
/..$/;say$&eq(th,st,nd,rd,(th)x6)[$_%100-$_%10-10&&$_%10]
```
[Try it online!](https://tio.run/##HYpRCsIwEET/c44YLCTpbjRS8QyeQESEFCpIWpP9qJd3zfZn5j1mlrG8I3Pvve4v9fnVZvzsabKVbE62JNukW0/dTT92COC2cgjGbHRnxkpqOIec1KGkRsfmkSaFICErolCQkEtow29e6DXnyu4aPSCwy38 "Perl 5 – Try It Online")
[Answer]
# [Haskell](https://www.haskell.org/), 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!](https://tio.run/##HctBCsMgEEDRfU4hEjBCF9HshJHeoxQZUWuosUWFZpG727Tb/3kR69On1HuYmGDKKLzezMXcOSAAbZEOYUJl@ZF8frRIrJYQiP3X3@YHarYwEHoeAtnBJ7@Rffy8iqt0PoGojcjsyFIc7RuuGd5lzW0MVJ68fwE "Haskell – Try It Online")
[Answer]
# [GeoGebra](https://geogebra.org), 56 bytes
```
s="1nd"
b=Ordinal(FromBase(Take(s,1,Length(s)-2),10))==s
```
[Try It On GeoGebra!](https://www.geogebra.org/calculator/s98dehwu)
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?
```
[Answer]
# [Vyxal](https://github.com/Vyxal/Vyxal), 7 bytes
```
₌Ǎ⌊∆o$c
```
[Try it Online!](https://vyxal.pythonanywhere.com/#WyIiLCIiLCLigozHjeKMiuKIhm8kYyIsIiIsIjIxbmQiXQ==)
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? 
```
[Answer]
# [Factor](https://factorcode.org/) + `math.text.english`, 37 bytes
```
[ 2 cut* swap dec> ordinal-suffix = ]
```
[![enter image description here](https://i.stack.imgur.com/FYAx9.gif)](https://i.stack.imgur.com/FYAx9.gif)
## Explanation
```
                ! "121th"
2 cut*          ! "121" "th"
swap            ! "th" "121"
dec>            ! "th" 121
ordinal-suffix  ! "th" "st"
=               ! f
```
[Answer]
# [Scala](http://www.scala-lang.org/), 51 bytes
Port of [@Kevin Cruijssen's Java answer](https://codegolf.stackexchange.com/a/162378/110802) in Scala.
[Try it online!](https://tio.run/##dZIxT8MwEIX3/IpTJrsSrs9hqpQiGNiYYENFMqnVFBknig8EIvz2EAkWlJfx3nBP33eXGx/91D2/hEbozp8ThQ8J6Zjpuu/pqyB695GanbqX4ZxO9f6m62LwSVNNU6732bx6adqQVWk2bKQdldk8PvFBXynOo0tjNYzzfFEdRJtST/PG7ZYehjdpP3fz0M9rJSbVqJKzlFr/z1w6LrJqWGaX0i4ytihkFDoUVihERQ4VOYiDeBwCcpgIIllUxRZ1sUVlbGHbikBoECvEDqFEhhYZamTokaFIXtEDOHTx@523Puaw@E50Y5BhYgiBGDACJoALeE0CvuXf5u9i@gE)
```
s=>s.matches(".*1.th|(.*[^1])?(1s|2n|3r|[^1-3]t).")
```
[Answer]
# [Python 2](https://docs.python.org/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!](https://tio.run/##NY5BDoMgFETX7Sno6kOriR9sIiSchLKwIUaTigb@pqe3tsXFzGTyZjHrm8Ylym2wj@3Vz8/Qs2yAckw0UggjuHmKvK2mSDy7Wnlx5RwauGXh6tZfLCAIYczdW7tzafw2LIkRmyJzgJmgYiBj@AZKGn@1JBaMzTFAVYjEY4KotVS4qyvW/atEhVLpRneNSgG8OZ/WtN9kVLGBk9g04gc "Python 2 – Try It Online")
[Answer]
# [05AB1E](https://github.com/Adriandmen/05AB1E), 24 bytes
```
0ìþR2£`≠*.•’‘vê₅ù•sèsáнQ
```
[Try it online!](https://tio.run/##ATkAxv8wNWFiMWX//zDDrMO@UjLCo2DiiaAqLuKAouKAmeKAmHbDquKChcO54oCic8Ooc8Oh0L1R//8xc3Q "05AB1E – Try It Online")
or as a [Test suite](https://tio.run/##MzBNTDJM/V9TVln53@DwmsP7gowOLU541LlAS@9Rw6JHDTMfNcwoO7zqUVPr4Z1AgeLDK4oPL7ywN/B/pdLh/Qq6dgqH9yvp/DcoyeAyLC7hMspL4TIFsQ1BhBGQMAILGxelcBkagEhTIDYCChmCxA2KUgA)
**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
```
[Answer]
# [Ruby](https://www.ruby-lang.org/), ~~42~~ 39 bytes
Lambda:
```
->s{s*2=~/1..h|[^1](1s|2n|3r|[4-90]t)/}
```
[Try it online!](https://tio.run/##VY7BDoIwEETP9is2cEANIFs8UBP1Q2pNVIqQmEJoSTQUfx1BTNTD7mZmXzJTN@dHn237YKdbvaTb5wrDMLf8iGKO2lJl49rydcAiYRarrq8ao8Ep1HAh2MG1NGBB3it5MTI9qINyCOGEe0hN7vlg6kYKf9TaePCjKap0@Genm/4AI/FvIDJGYxwm@axkkhRjpDGLWBIl3xgiQnm65JCWYAsfSktmU1u3LbqxrNtmvBCwf@OwmbK6ob/blp1DpEr7Fw)
User input:
```
p gets*2=~/1..h|[^1](1s|2n|3r|[4-90]t)/
```
[Try it online!](https://tio.run/##KypNqvz/v0AhPbWkWMvItk7fUE8voyY6zjBWw7C4xiivxrioJtpE19IgtkRT//9/Q6O8FAA)
Matches:
* `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](https://www.ruby-lang.org/) `-n`, 35 bytes
```
p~/1..h|([^1]|^)(1s|2n|3r|[4-90]t)/
```
[Try it online!](https://tio.run/##KypNqvz/v6BO31BPL6NGIzrOMLYmTlPDsLjGKK/GuKgm2kTX0iC2RFP//39Do7yUf/kFJZn5ecX/dfMA)
Same idea as above but instead of duplicating the string, this also matches the start of the string (`^`).
[Answer]
# 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.
---
[Answer]
# [Wolfram Language (Mathematica)](https://www.wolfram.com/wolframscript/), 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!](https://tio.run/##PU29CoNADH6XTApiTeygg1BoOxZaOorD4flzg1Y0Q6H2Xt3GUxpIwveXdIrbqlNsSrXU2fLk0fTNTXHZPnLP09nFNIbPrRpVydXoh2FoLSBYr8qu77Ia2LfaCsqFnIGkYwh0ISZuwZ89wIlXoderNGrwhdT/M2vYOYvlLp85rw@nD6BUmlKM0sk@kg0SxkhxGqVJlEguAEI5vS75E4B@NTKRnITkJIW4wWhfzkk0DS6njkJ/i@UH "Wolfram Language (Mathematica) – Try It Online")
[Answer]
# [Wolfram Language (Mathematica)](https://www.wolfram.com/wolframscript/), ~~65~~ 59 bytes
```
SpokenString@p[[#]]~StringTake~{5,-14}&@@ToExpression@#==#&
```
[Try it online!](https://tio.run/##y00syUjNTSzJTE78n2ar8T@4ID87NS@4pCgzL92hIDpaOTa2DsILScxOras21dE1NKlVc3AIyXetKChKLS7OzM9zULa1VVb7r8kVAFRY4pDmoGRoZFhcooTCL0pB5huiyRsalmQo/QcA "Wolfram Language (Mathematica) – Try It Online")
Of course Mathematica has a built-in (although undocumented) for converting to ordinal number. [Source](https://mathematica.stackexchange.com/questions/64860/built-in-way-to-convert-integer-to-ordinal-string).
(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](https://codegolf.stackexchange.com/a/162954) for a non-builtin version.
[Answer]
# 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](http://sandbox.onlinephpfunctions.com/code/eeb131319f5734a74a0654e85ed3d3ea7f3b2a6b). (TiO wrapped as function for convenience)
[Answer]
# 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!](https://tio.run/##rVZtb@JGEP5s/4oRusgmwcQ2VxXw5SpVvarf7gcEhNbrNThn76LdNXGV8tvTmTUmEN2p/XBIMfvyzDMzz8yY5MzsXjmz8OnTl69/wme4t83@nk2ZaSADSV9RKepyll7eRKrfcVWIqUKg4DsFI190VmgJlbSw2TBrdZW3Vmw2YVgyYzmr6/EYylbykO@Yvh1n4DeskuH4xXcHVhhrHtcPLyt/NUoTY1ejCa6SVBan1XmR2t1pdUZdrJJksUhnCf7NT495v02TWZLOFvFiHs8Hhvl59UsyrNKzy/lMDz7fVnGPi/1j5pdKh5Rx9RBnfQLVOqvu7jCpvcaLMlyNbswSboqVRKMBMnE6DLvxOPOP/nE0VMDYNp/yDHXwt5xfC47luARdlSKSKtpXAqJmljprlT8VbbOH6I8rHLopxAEZCqH1NdBcAf8BK8QlGE@2WiDuCwSPcbRYv3w8QoCnvLUQ5fBr9DElKw1RAcFKBm8bB3N05GAnuqntLB49Y4Icv9nzNwhenGjwIblPj4TvNYTRSt7e3v7@txXAVSvtEncreWNWcgQfQurgaPydrFxjRuJk/pfoKEeyRaUvsdB1BUQY5v59cHTx@T2trRpxXZFXI7itlISpxSnwvW2tcla7ZvfpsfTok9mdcGcO2hphYJgMTEsehKQL4/dgwbuHstLGAtNbsOrNMtyjpHhQyT2qbnDQ5Hbse53SIFg3oQcRaGGEpQ1hY9/TghUbRC/RQSU5jq2D4VKLBn3DibYn9BHFsSNyEv0RsesJBHGAp081DFRkXgKlRRMMlZEBhgtFta3sBLboXqIe/f49311CjKxnFD@iZCW@VNzWkZxd1MLixX/5iJyPxPkQ2HzSAPIncZQsYMcORFqWQlPyuq2FmYBR8ESDgDoEhCdTJ2xOwubdm675oKvXqAPk9eRCKgAvo8h4qx15HzuTBYqrtMDKoQGEtXrujVRJfFhDz7R57yvuZjE6c32h0dbwqjrxoFvZNrnQJ98F@n6slfrW7jeW5bW4Q4Y19BEojIBbULqoJLWkVg30WHDY/5Gfa5bcNcvFTfLdghY11s@4gg0uhxAY5@7INTMBLsP4zbEpLEnfwi6SUyMMRMh7YHVVYJVOjR1qYVv8zdFiO3YxAb6MBymfMDghCwp7KCmx4RHpfTWLVJmeitqe6r78yboENvCyQuHIYwEpBIzT7n561r6H3Euadev17wCHJD4kaV3nIYV/fl8VzDLfu2weKHKM1kht3Sd4xX8NXv8F "Bash – Try It Online")
[Answer]
# 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 ✓
```
]