Ordinal string check

17

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

Muhammad Salman

Posted 2018-04-14T15:46:05.553

Reputation: 2 361

Can you clarify the rules of ordinal numbers? Or at least put a link to what the rules you are following. – qwr – 2018-04-14T17:00:54.793

They are normal rules. I changed nothing. But thanks for the input , I added a link – Muhammad Salman – 2018-04-14T17:02:50.977

@Jonathan Allan Ordinal numbers start from 1st, negative ordinals do not exist - https://english.stackexchange.com/questions/309713/ordinal-form-of-negative-numbers-especially-1-2-3

– Oliver Ni – 2018-04-14T23:18:44.557

@JonathanAllan OP says "Input is going to be valid ordinal pattern." which means no negatives – Oliver Ni – 2018-04-15T05:12:25.157

I updated the description. – Muhammad Salman – 2018-04-15T05:24:46.677

@JonathanAllan : Yes I mean that every given input will be in the format 1st so digit along with one of the four st , nd , rd , th. – Muhammad Salman – 2018-04-15T12:43:01.610

@JonathanAllan : The question has been updated – Muhammad Salman – 2018-04-15T13:30:46.433

I'd recommend changing true/false values to truthy/falsey values and removing the excess part about output being a string or array (unless you actually mean input??)

– Jo King – 2018-04-20T12:54:22.220

@JoKing : I don't remember it being here. I will remove that. as for number 1 ok will do that too – Muhammad Salman – 2018-04-20T13:11:49.970

2You 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. – David Conrad – 2018-04-20T21:05:34.200

When an ordinal is written we tend to see the suffix and assume it's an ordinal without checking whether that suffix is correct. I often write things like "4rd" just to see if anybody notices. – Esolanging Fruit – 2018-04-22T06:28:16.337

Answers

3

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!

Digital Trauma

Posted 2018-04-14T15:46:05.553

Reputation: 64 644

What ? this is not outputting the correct answer. – Muhammad Salman – 2018-04-14T16:58:09.423

@MuhammadSalman That's because it's a test suite. Take a look at the exit codes for 1st and 1th.

– Dennis – 2018-04-14T17:09:18.187

egrep is capable of addition and primality testing (in unary), so I think you can make this an egrep answer. – Dennis – 2018-04-14T17:10:27.017

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 ? – Muhammad Salman – 2018-04-14T17:13:04.740

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

– manatwork – 2018-04-14T17:15:02.633

@MuhammadSalman Bash is a scripting language; it has exit codes. if egrep '((^|[^1])(1st|2nd|3rd)|(1.|(^|[^1])[^1-3])th)$' can be used in conditionals. – Dennis – 2018-04-14T17:19:44.807

I still fail to see why he is not outputting "true" or "false" instead of what he is doing. And I just checked and Bash does have booleans. so return true or false instead of deleting them. But whatever I guess this works as well. Thanks @Dennis for explanation. Would you like to answer I would like to see yours. – Muhammad Salman – 2018-04-14T17:23:37.110

Thanks but no thanks , like I said I ain't got a clue when it comes to Bash I have already accepted this as an acceptable answer. – Muhammad Salman – 2018-04-14T17:40:11.447

3

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!

DanielIndie

Posted 2018-04-14T15:46:05.553

Reputation: 1 220

If the assumption reg solution can also be ***. – l4m2 – 2018-04-14T20:12:28.320

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 – l4m2 – 2018-04-14T23:29:05.963

3

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

Jonathan Allan

Posted 2018-04-14T15:46:05.553

Reputation: 67 804

st, nd, rd and th all have a unique letter so you can just test if it occurs in the string 53 bytes

– Asone Tuhid – 2018-04-17T16:14:52.000

@AsoneTuhid nice golf - thanks! – Jonathan Allan – 2018-04-17T16:41:01.270

@AsoneTuhid - also saved three on my Jelly answer, so double the thanks! – Jonathan Allan – 2018-04-17T16:47:37.973

3

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

Kevin Cruijssen

Posted 2018-04-14T15:46:05.553

Reputation: 67 575

2

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!

Herman L

Posted 2018-04-14T15:46:05.553

Reputation: 3 611

2

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

hakr14

Posted 2018-04-14T15:46:05.553

Reputation: 1 295

2

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.

Oliver Ni

Posted 2018-04-14T15:46:05.553

Reputation: 9 650

1

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

Jonathan Allan

Posted 2018-04-14T15:46:05.553

Reputation: 67 804

1

Perl 5 -n, 57 bytes

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

Try it online!

Xcali

Posted 2018-04-14T15:46:05.553

Reputation: 7 671

1

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!

Post Rock Garf Hunter

Posted 2018-04-14T15:46:05.553

Reputation: 55 382

90 bytes: Try it online!

– Laikoni – 2018-04-21T12:06:42.193

0

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!

Chas Brown

Posted 2018-04-14T15:46:05.553

Reputation: 8 959

0

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

Emigna

Posted 2018-04-14T15:46:05.553

Reputation: 50 798

0

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

Asone Tuhid

Posted 2018-04-14T15:46:05.553

Reputation: 1 944

0

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)

Engineer Toast

Posted 2018-04-14T15:46:05.553

Reputation: 5 769

0

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!

Kelly Lowder

Posted 2018-04-14T15:46:05.553

Reputation: 3 225

0

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.

user202729

Posted 2018-04-14T15:46:05.553

Reputation: 14 620

0

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!

Logern

Posted 2018-04-14T15:46:05.553

Reputation: 845

0

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)

Titus

Posted 2018-04-14T15:46:05.553

Reputation: 13 814

-1

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 ✓

Toby Speight

Posted 2018-04-14T15:46:05.553

Reputation: 5 058