This exercise is kind of fun. For any given array of numbers, we want to return an array of ordinal numbers. For example, given an array of [ 1, 2, 3 ], we want to return [ '1st', '2nd', '3rd' ].

Three cyclists racing

How do we do this?

As anything else, there are always a number of approaches:

  1. Use a look-up array
  2. Convert the number into an array, then check the last member
  3. Use the built-in Intl object

Let’s start with the look-up array solution. We’ll start with an array consisting of three members:

1
2
const sourceArray = [1,2,3];
const lookupArray = ['st', 'nd', 'rd', 'th'];

Now, we’ll run the Array.prototype.map() function on our array of numbers, to return their ordinal counterparts:

1
2
3
const sourceArray = [1,2,3];
const lookupArray = ['st', 'nd', 'rd', 'th'];
sourceArray.map(member => member + lookupArray[member])

The above will return:

(3) ["1nd", "2rd", "3th"]

Our code works, but we have an off-by-1 logical error in our code. In other words, instead of the given result, we need this array returned:

(3) ["1st", "2nd", "3rd"]

This is an easy fix:

1
2
3
4
var arr = [1,2,3];
var lookup = ['st','nd','rd','th'];
arr.map(member => member + lookup[member-1])
// (3) ["1st", "2nd", "3rd"]

Now we’re getting back the expected result. What we can conclude from the look-up array approach is that it will work great for smaller arrays of numbers. It’s a quick and easy solution when we need to convert a short array of numbers to its ordinal counterpart. However, how do we deal with longer arrays of numbers? What do we do if our array’s length is a lot larger, say, in the hundreds? For that we’ll use the second solution.

The second solution is Convert the number into an array, then check the last member. Let’s start from the core of this solution: instead of working on an array of numbers, we’ll work with a single number. So:

1
2
3
4
5
6
7
function addOrdinalSuffix(num) {
    console.log(num + 'th');
}

let userPickedNum = prompt('Type a number');

addOrdinalSuffix(userPickedNum);

The above code works perfectly for a majority of cases. But what if a user types, for example, 4321? This is the output:

4321th

This is obviously not the correct solution; it should have been 4321st. So let’s take the number 4321 and convert it into an array:

4321.toString().split(""); // Uncaught SyntaxError: Invalid or unexpected token

Why are we getting the above error? Because the single dot after 4321 stands for the decimal delimiter. Luckily, the fix here is really easy:

4321..toString().split(""); // ['4','3','2','1']

Great, now we’re successfully splitting our number into an array of characters. Next, let’s log out only the last member of the array (i.e the last digit of our number):

1
2
3
4
5
6
7
8
9
10
function addOrdinalSuffix(num) {
    let arr = num.toString().split('');
    console.log(arr);
    // console.log(num + 'th');
}

let userPickedNum = prompt('Type a number');

addOrdinalSuffix(userPickedNum);
// returns: (4) ["4", "3", "2", "1"]

Now let’s log out only the last member of the array, using:

console.log(arr[arr.length - 1])

Here’s the full updated code:

1
2
3
4
5
6
7
8
9
10
function addOrdinalSuffix(num) {
    let arr = num.toString().split('');
    console.log(arr[arr.length - 1]);
    // console.log(num + 'th');
}

let userPickedNum = prompt('Type a number');

addOrdinalSuffix(userPickedNum);
// returns: 1

Now all we need to do is map the suffixes like this:

  • if we get a 1, we return 1st
  • if we get a 2, we return 2nd
  • if we get a 3, we return 3rd

Here’s the updated code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function addOrdinalSuffix(num) {
    let arr = num.toString().split('');
    let arrLastMember = arr[arr.length - 1];
    let suffix = 'th';

    if (arrLastMember == 1) {
        suffix = 'st';
    }
    if (arrLastMember == 2) {
        suffix = 'nd';
    }
    if (arrLastMember == 3) {
        suffix = 'rd';
    }
    
    console.log(num + suffix);
}

let userPickedNum = prompt('Type a number');

addOrdinalSuffix(userPickedNum);
// returns: 4321st

At this point, our task is seemingly finished, but there’s a weird edge case with teens. If we ran the above code and passed, for example, number 11 to the prompt, we’d get back: 11st. This is wrong; we should have returned 11th.

The solution for this is to check the last two digits instead of one. Then, if the last two digits are 11, 12, or 13, we’ll return a th suffix, and for all other cases, we’ll return the appropriate st, nd, or rd.

Here’s the natural update to our code. Note: this code can be refactored; for now, let’s just think about the logic and not focus too much on optimizing our code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function addOrdinalSuffix(num) {
    let arr = num.toString().split('');
    let arrLastMember = arr[arr.length - 1];
    let arrSecondLastMember = arr[arr.length - 2];
    let suffix = 'th';

    console.log('Type is:', 
        typeof arrSecondLastMember.concat(arrLastMember) 
        // returns: "type is: string"
    );

    console.log('The last two digits are:', 
        arrSecondLastMember.concat(arrLastMember)
    );

    if (arrLastMember == 1 && 
        arrSecondLastMember.concat(arrLastMember) != "11") {
        
        suffix = 'st';
    }
    if (arrLastMember == 2 &&
        arrSecondLastMember.concat(arrLastMember) != "12") {
        
        suffix = 'nd';
    }
    if (arrLastMember == 3 &&
        arrSecondLastMember.concat(arrLastMember) != "13") {

        suffix = 'rd';
    }
    
    console.log(num + suffix);
}

let userPickedNum = prompt('Type a number');

addOrdinalSuffix(userPickedNum);

Great, our code works fully, and also takes care of edge cases!

To confirm that that is really the case, we could test our improved code by passing random numbers on each function call: 4311, 4312, 4313, 4321, 101, 111, 1001, 1011, 1515.

However, there’s even a better way to do it. We’ll simply write some code to run our addOrdinalSuffix() function on an array of numbers. Here’s the updated code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function addOrdinalSuffix(num) {
    let arr = num.toString().split('');
    let arrLastMember = arr[arr.length - 1];
    let arrSecondLastMember = arr[arr.length - 2];
    let suffix = 'th';

    console.log('Type is:', 
        typeof arrSecondLastMember.concat(arrLastMember) 
        // returns: "type is: string"
    );

    console.log('The last two digits are:', 
        arrSecondLastMember.concat(arrLastMember)
    );

    if (arrLastMember == 1 && 
        arrSecondLastMember.concat(arrLastMember) != "11") {
        
        suffix = 'st';
    }
    if (arrLastMember == 2 &&
        arrSecondLastMember.concat(arrLastMember) != "12") {
        
        suffix = 'nd';
    }
    if (arrLastMember == 3 &&
        arrSecondLastMember.concat(arrLastMember) != "13") {

        suffix = 'rd';
    }
    
    console.log(num + suffix);
}

let arrayOfNumbers = [
    `4311`,
    `4312`,
    `4313`,
    `4321`,
    `101`,
    `111`,
    `1001`,
    `1011`,
    `1515`
];

arrayOfNumbers.forEach(member => {
        console.log('Number:', member);
        addOrdinalSuffix(member);
    }
);

Here’s the output of the above code:

Number: 4311
Type is: string
The last two digits are: 11
4311th

Number: 4312
Type is: string
The last two digits are: 12
4312th

Number: 4313
Type is: string
The last two digits are: 13
4313th

Number: 4321
Type is: string
The last two digits are: 21
4321st

Number: 101
Type is: string
The last two digits are: 01
101st

Number: 111
Type is: string
The last two digits are: 11
111th

Number: 1001
Type is: string
The last two digits are: 01
1001st

Number: 1011
Type is: string
The last two digits are: 11
1011th

Number: 1515
Type is: string
The last two digits are: 15
1515th



Note:
This exercise comes from Book 3 of my book series on JS, available on Leanpub.com.