17
\$\begingroup\$

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

\$\endgroup\$
  • \$\begingroup\$ Can you clarify the rules of ordinal numbers? Or at least put a link to what the rules you are following. \$\endgroup\$ – qwr Apr 14 '18 at 17:00
  • \$\begingroup\$ They are normal rules. I changed nothing. But thanks for the input , I added a link \$\endgroup\$ – Muhammad Salman Apr 14 '18 at 17:02
  • \$\begingroup\$ @Jonathan Allan Ordinal numbers start from 1st, negative ordinals do not exist - english.stackexchange.com/questions/309713/… \$\endgroup\$ – Oliver Ni Apr 14 '18 at 23:18
  • \$\begingroup\$ @JonathanAllan OP says "Input is going to be valid ordinal pattern." which means no negatives \$\endgroup\$ – Oliver Ni Apr 15 '18 at 5:12
  • 2
    \$\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\$ – David Conrad Apr 20 '18 at 21:05

19 Answers 19

3
\$\begingroup\$

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!

\$\endgroup\$
  • \$\begingroup\$ What ? this is not outputting the correct answer. \$\endgroup\$ – Muhammad Salman Apr 14 '18 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 Apr 14 '18 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 Apr 14 '18 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\$ – Muhammad Salman Apr 14 '18 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 Apr 14 '18 at 17:15
3
+100
\$\begingroup\$

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!

\$\endgroup\$
  • \$\begingroup\$ If the assumption reg solution can also be ***. \$\endgroup\$ – l4m2 Apr 14 '18 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 Apr 14 '18 at 23:29
3
\$\begingroup\$

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

\$\endgroup\$
  • \$\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\$ – Asone Tuhid Apr 17 '18 at 16:14
  • \$\begingroup\$ @AsoneTuhid nice golf - thanks! \$\endgroup\$ – Jonathan Allan Apr 17 '18 at 16:41
  • \$\begingroup\$ @AsoneTuhid - also saved three on my Jelly answer, so double the thanks! \$\endgroup\$ – Jonathan Allan Apr 17 '18 at 16:47
3
\$\begingroup\$

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
\$\endgroup\$
2
\$\begingroup\$

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])
\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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!

\$\endgroup\$
1
\$\begingroup\$

Perl 5 -n, 57 bytes

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

Try it online!

\$\endgroup\$
1
\$\begingroup\$

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!

\$\endgroup\$
1
\$\begingroup\$

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
\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
0
\$\begingroup\$

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
\$\endgroup\$
0
\$\begingroup\$

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:

  • 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

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

\$\endgroup\$
0
\$\begingroup\$

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)

\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
0
\$\begingroup\$

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.

\$\endgroup\$
0
\$\begingroup\$

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)

\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
-1
\$\begingroup\$

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 ✓
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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