JS - Arrays

aigle-levant · September 4, 2024

Let us learn about an important component of programming while studying JS…

Arrays

An array is an ordered collection of items.

const spotifyTopTen = [
    "The Nights", "Paint My Soul",
    "Send Them Off!", "Everlasting",
    "Good Grief", "Hangin'",
    "Soy Yo", "Walk",
    "Hey Brother", "My Blood"
]

Declaring arrays

We normally declare arrays with const. We can alternatively use the new keyword [I frequently use the former method since it’s easier].

const spotifyTopTen = new Array(
    "The Nights", "Paint My Soul",
    "Send Them Off!", "Everlasting",
    "Good Grief", "Hangin'",
    "Soy Yo", "Walk",
    "Hey Brother", "My Blood"
);

Now here’s a warning : do not use new Array() to create arrays using a single number. You’ll see here why :

const song = new Array(45);
console.log(song);
//[ <45 empty items> ]

When we create an array using a single number, we get that number’s worth of undefined elements as well.

This doesn’t work on any other type, however :

const song = new Array(4.5);
console.log(song);

/*
RangeError: Invalid array length
at Object.<anonymous> (...\js\ohohoh.js:10:14)
*/
const song = new Array("This has to be an array, at least");
console.log(song);
//[ 'This has to be an array, at least' ]

Array operations

Accessing elements

We access elements using the array’s name and index of the item either by using its index…

console.log(spotifyTopTen[3]); //Everlasting
console.log(spotifyTopTen[7]); //Walk

…Or by using at() :

console.log(spotifyTopTen.at(3)); //Everlasting

Let’s try accessing the last element of array :

console.log(spotifyTopTen[-1]); //undefined

This happens as JS [just like its namesake] doesn’t support negative indexing. If this were python, you’d have gotten the last item of the array. Let’s try another way :

console.log(spotifyTopTen[spotifyTopTen.length - 1]);
//My Blood

Or we simply use array.at(-1) to return the last item of the list.s

Modifying elements

And we can also modify an array’s values using its index :

console.log(spotifyTopTen[9]); //My Blood
spotifyTopTen[9] = "Ride";
console.log(spotifyTopTen[9]); //Ride

Array to string

toString() must be a familiar method to those using Java. Here in JS, however, this method converts an array to a string of values separated by commas :

console.log(spotifyTopTen.toString());
/*
The Nights,Paint My Soul,Send Them Off!,Everlasting,Good Grief,Hangin',Soy Yo,Walk,Hey Brother,My Blood
*/

Looks a bit too close for comfort; let’s fix it :

console.log(spotifyTopTen.join(", "));
/*
The Nights, Paint My Soul, Send Them Off!, Everlasting, Good Grief, Hangin', Soy Yo, Walk, Hey Brother, My Blood
*/

join() allows me to do what toString() does, but better.

Looping through an array

We normally use a for loop to print items of a list :

for (let i=0; i<spotifyTopTen.length; i++)
{
    console.log(spotifyTopTen[i]);
}

You can achieve the same using a while loop as well, but it is a bit tedious process :

let i = 0;
while (i<spotifyTopTen.length)
{
    console.log(spotifyTopTen[i]);
    i++;
}

Now there exist other loops in JS quite suited for looping over a list.

Adding array elements

push() lets you add elements to your arrays at the beginning of the array. So does another method involving length :

spotifyTopTen.push("Call It What You Want");

spotifyTopTen[spotifyTopTen.length] = "Song 2";

let len = spotifyTopTen.length;
console.log(spotifyTopTen + '\n\nLength is: ' + len);

Another way of doing this is unshift() :

spotifyTopTen.unshift("Song 2");
console.log(spotifyTopTen);
console.log("Length: " + spotifyTopTen.length);

/*
'Song 2',
'Paint My Soul',
'The Nights',
...
Length: 11
*/

Just a warning : always create elements with their indexes next to each other. Not doing so creates undefined holes in array :

spotifyTopTen[15] = "Water Ripples";
console.log(spotifyTopTen);

/*
[
  'The Nights',     'Paint My Soul',
  'Send Them Off!', 'Everlasting',
  'Good Grief',     "Hangin'",
  'Soy Yo',         'Walk',
  'Hey Brother',    'My Blood',
  <5 empty items>,  'Water Ripples'
]
*/

See the ‘5 empty items’? This is what I’m talking about. When we try accessing them, we get undefined.

Removing array elements

Now let’s remove the extra fittings in our array. Say, we now want the Spotify Top 5 to remain and the rest to be gone. We shall use pop() method :

for (let i=spotifyTopTen.length-1; i>=5; i--)
{
    spotifyTopTen.pop(spotifyTopTen[i]);
}
console.log(spotifyTopTen);

/*
[
  'The Nights',
  'Paint My Soul',
  'Send Them Off!',
  'Everlasting',
  'Good Grief'
]
*/

We used a for-loop that initialises at the last element of the array and does pop() on the element until we get only 5 elements in the array left. Then we return the finished array.

Do keep in mind that push() and pop() do their operations in the end of the array and not in the middle of it.

Another way of doing this is shift() :

let removedElement = spotifyTopTen.shift();
console.log(removedElement + '\n');
console.log(spotifyTopTen);

Multi-dimensional arrays

Most arrays are single-dimensional [having only a single row]. We shall now start making arrays with more than one dimensional [aka multi-dimensional arrays].

Suppose, I want the top 5 songs of 2 of my favourite artists in a single array. I’d try approaching this problem like this :

//songs to be used
const bastille = ["Good Grief", "Winter of our Youth", "Pompeii", "Bad Blood", "Weight Of Living Pt. II"];
const saintMotel = ["My Type", "Move", "Cold Cold Man", "Getaway", "Sweet Talk"]

const songs = []; //empty array
while (bastille.length)
{
    songs.push(bastille.splice(0));
    songs.push(saintMotel.splice(0));
}

/*
[
  'Good Grief',
  'Winter of our Youth',
  'Pompeii',
  'Bad Blood',
  'Weight Of Living Pt. II'
]
[ 'My Type', 'Move', 'Cold Cold Man', 'Getaway', 'Sweet Talk' ]
*/

We use another array’s length to specify the condition for our while loop and add the array using splice(). Since we didn’t mention the 2nd parameter, no element will be removed.

while (bastille.length)
{
    songs.push(bastille.splice(0, 1));
    songs.push(saintMotel.splice(0, 1));
}
/*
[ 'Good Grief' ]
[ 'My Type' ]
[ 'Winter of our Youth' ]
[ 'Move' ]
[ 'Pompeii' ]
[ 'Cold Cold Man' ]
[ 'Bad Blood' ]
[ 'Getaway' ]
[ 'Weight Of Living Pt. II' ]
[ 'Sweet Talk' ]
*/

Now we’ve got a nice zig-zagged pattern of array elements.

There exist other methods to define a multi-dimensional array :

const song = [bastille, saintMotel];

for (let i = 0; i < song.length; i++)
{
    console.log(song[i]);
}

This method packs the arrays into a main array, essentially making them sub-arrays. At least, that’s how multi-dimensional arrays work.

const song =
[
    ["Good Grief", "Winter of our Youth", "Pompeii", "Bad Blood", "Weight Of Living Pt. II"],
    ["My Type", "Move", "Cold Cold Man", "Getaway", "Sweet Talk"]
]

for (let i = 0; i < song.length; i++)
{
    console.log(song[i]);
}

And here we are : the most commonly used method to create a multi-dimensional array.

Accessing elements in multidimensional arrays

Now that we have not one, but 2 axes to take care of, accessing becomes a biiit trickier than before. But we can take care of that :

console.log(song[1]);
/*
["My Type", "Move", "Cold Cold Man", "Getaway", "Sweet Talk"]
*/
console.log(song[1][1]); //Move

The 1st one specifies the sub-array to access; the 2nd specifies which element in that sub-array to access. If we don’t specify the 2nd part, JS just says ‘fuck it’ and returns the entire sub-array for you to look up.

Map method

Consider the following proposition : you give the console 10 lines of lyrics and it returns you the vowels in those lyrics. Sounds fun, right?

function getVowelsFromLyrics(array)
{
    let vowelCount = 0;
    for (let i=0; i<array.length; i++)
    {
        if (array.includes("a" || "e" || "i" || "o" || "u" || "A" || "E" || "I" || "O" || "U"))
        {
            vowelCount += 1;
        }
    }
    return vowelCount;
}

Well, we could make this easier using the map method. It requires a function as an argument [this function is called a callback].

This method applies the function to every single element of your array and creates an new array with those modified values.

const prompt = require("prompt-sync")({sigint:true});
const arr = [];

console.log("Enter your lyrics and I'll find out the vowels in each line!");

for (let i=0; i<=10; i++)
{
    if (i==9)
    {
        console.log("Almost there! Just one more and we're done.");
    }
    let input = prompt("");
    arr.push(input);
}
const arrMap = arr.map(getVowelsFromLyrics);

console.log(arrMap);

Filter method

filter lets you iterate through the array and apply the callback on every array element. However, it only includes the values that the callback returns as true. False values are excluded.

Consider the following function. It returns true if the age passed is below 18 [i.e. the person is a minor] and returns false if not.

function isNotAdult(age)
{
    return age<18;
}

Now we use this to evaluate an array’s elements :

const ageGroup = [10, 20, 2, 18, 17];
const ageGroupFiltered = ageGroup.filter(isNotAdult);

console.log(ageGroup); //[ 10, 20, 2, 18, 17 ]
console.log(ageGroupFiltered); //[ 10, 2, 17 ]

See! The ones that don’t pass the condition are filtered out [excluded].

Reduce method

reduce is another method similar to the likes of filter and map. However, it takes 2 arguments instead of one.

The second argument is the initial value from which the function should start.

Let’s try it with our factorial function :

const arr = [1, 2, 3, 4, 5, 6];

const fact = (function f(num)
{
    if (num===0 || num===1)
    {
        return 1;
    }
    return num * f(num-1);
})

const factArr = arr.map(fact);
const factArrProduct = factArr.reduce((prod, num) => prod*num, 1);
console.log(factArr); //[ 1, 2, 6, 24, 120, 720 ]
console.log(factArrProduct); //24883200

We made a new array from the factorial of the array elements. Then we simply multiplied each one of them using reduce, starting from 1.

Problem

Create a function sumOfTripledEvens that does the following :

  • Take in an array.
  • For every even number, it will triple it.
  • Then it will sum all those even numbers.

You must use reduce, map and filter methods.

function checkIfNumIsEven(num)
{
    return num%2==0;
}
function tripleNum(num)
{
    return num*3;
}

function sumOfTripledEvens(array)
{
    return array
    .filter(checkIfNumIsEven)
    .map(tripleNum)
    .reduce((counter, number) => counter + number);
}

const arr = [10, 20, 3, 4, 2, 445, 231, 776];
console.log(sumOfTripledEvens(arr)); //2436

You must be asking why would I write 2 more functions instead of doing it like this :

function sumOfTripledEvens(array)
{
    return array
    .filter((num) => num%2==0)
    .map((num) => num*3)
    .reduce((counter, number) => counter + number);
}

const arr = [10, 20, 3, 4, 2, 445, 231, 776];
console.log(sumOfTripledEvens(arr));

I could just finish it in a single way [even more so if I use range() and other methods]. However, we must learn to abstract some of these details away so that our code may look elegant and readable.

Problem - Array Cardio : Wes Bos

Here’s some problems from Wes Bos’ repository.

Filter the list of inventors for those who were born in the 1500’s

//function to check year
function checkYear(array)
{
    return (array.year>=1500 && array.year<1600);
}

//filter out entries that fail the condition
const inventorsFilterYear = inventors.filter(checkYear);
console.log(inventorsFilterYear);

Give us an array of the inventors first and last names

//return first and last name using backticks
function getFullName(array)
{
    return (`${array.first} ${array.last}`);
}

//apply function to every item in list
const inventorsFullName = inventors.map(getFullName);
console.log(inventorsFullName);

If for people…

//split names into array
function splitArray(array)
{
    const arr = [];
    for (let i=0; i<people.length; i++)
    {
        arr.push(people[i].split(','));
    }
    return arr;
}

//return the re-arranged names
function getName(array)
{
    return (`${array[1]} ${array[0]}`);
}

//call getName() for every element
const peopleOrganised = splitArray(people).map(getName);
console.log(peopleOrganised);

Sort the inventors by birthdate, oldest to youngest

function sortByBirthDate(personOne, personTwo)
{
    if (personOne.year>personTwo.year)
    {
        return 1;
    }
    return -1;
}

const inventorsBirthDateSorted = inventors.sort(sortByBirthDate);

console.log(inventorsBirthDateSorted);

How many years did all the inventors live all together?

const sumOfInventorYear = inventors.reduce(((counter, inventors) => counter + (inventors.passed - inventors.year)), 0);

console.log(sumOfInventorYear);

Sort the inventors by years lived

function yearsLived(array)
{
    return (array.passed - array.year);
}
function sortByYearsLived(personOne, personTwo)
{
    let compare = (yearsLived(personOne)>yearsLived(personTwo))?1:-1;
    return compare;
}

const inventorsSortedByYearsLived = inventors.sort(sortByYearsLived);
console.log(inventorsSortedByYearsLived);

create a list of Boulevards in Paris that contain ‘de’ anywhere in the name