JavaScript array methods 2/3 - iterating arrays
As we know, arrays are collections of elements. JavaScript arrays have something named iteration methods - these methods operate on every element of the collection and can help us with creating new arrays based on individual entries of our original array or just simply do something with every single element. In this part of our JavaScript array methods series, we are going to cover them in deep.
Table of contents
- Looping through an array
- Modify all elements of an array - mapping arrays
- Reducing arrays
- Check if every element fulfils your condition
- Check if any element fulfils your condition
- Filtering arrays
Looping through an array
Looping (or iterating) through an array in most languages is commonly done using a for-loop. JavaScript is not different.
const images = [
"https://image-cdn.com/my-image-1.jpeg",
"https://image-cdn.com/my-image-2.jpeg",
"https://image-cdn.com/my-image-3.jpeg",
];
for (let i = 0; i < images.length; ++i) {
console.dir(images[i]);
}
This code is going to output every single URL in the images array. As you see, our iteration is working, nothing special. It may look familiar to you if you worked with languages other than JavaScript.
However, it's not the only way to loop through our array. The Array prototype has implemented a forEach
method, which calls a callback on every element of an array.
const images = [
"https://image-cdn.com/my-image-1.jpeg",
"https://image-cdn.com/my-image-2.jpeg",
"https://image-cdn.com/my-image-3.jpeg",
];
images.forEach((image) => console.dir(image));
NOTE. You can access the index of the current element by passing a second argument to the function inside forEach
.
The result is the same - we've printed every element of this array. Although there is a difference between a classic for loop and a forEach
- performance. forEach
may be more convenient, but it's slower, so when you're dealing with big arrays you shouldn't use it.
In these benchmark results, you can see that there is a third method of iteration, which is a bit faster than forEach
and more friendly than a classic for
loop - I'm talking about for...of
. It was introduced after for
and forEach
and works...
const images = [
"https://image-cdn.com/my-image-1.jpeg",
"https://image-cdn.com/my-image-2.jpeg",
"https://image-cdn.com/my-image-3.jpeg",
];
for (const image of images) {
console.dir(image);
}
...the same way - I mean, it produces the same result. Being slightly more performant than forEach
it's a better choice in most cases. Furthermore, opposite to forEach
, it can be controlled with statements like break
, but this article is not about loops, so we're going to stop talking about for...of
.
Modify all elements of an array - mapping arrays
Sometimes you'll need to transform every single element of your array and create a new array with these elements. In this case, map
is the remedy. It simply runs a callback on every element and then creates a new array from the results.
const names = ["dave", "emma", "alan", "simon", "stacy"];
const capitalizedNames = names.map((name) => {
return name[0].toUpperCase() + name.slice(1);
});
console.dir(capitalizedNames); // Output: ["Dave", "Emma", "Alan", "Simon", "Stacy"]
This example is going to capitalize the first letter of every word in the array and return a new array consisting of capitalized words.
NOTE. You can access the index of the current element by passing a second argument to the function inside map
.
With using map
comes one thing that you need to remember - the resulting array is the same length as the original array and every missing element is just changed to undefined
. It can occur in a case like this:
const array = ["1", "6", "17", "boo!", "32"];
const numbers = array.map((x) => {
const n = +x; // It's just a short way to cast a string into number
if (!isNaN(n)) {
return n;
}
});
console.dir(numbers); // Output: [1, 6, 17, undefined, 32]
In this example, we are converting an array of numeric strings to an array of numbers. There is only one problem, when conversion fails we get a NaN, and our statements under the condition are never called so this iteration never returns a value, in this case, map
is going to return an undefined for that element.
Mapping and flattening?
Now, as we already covered map
, let's talk about flatMap
, which works like map
followed by flat
. Let's assume we have a text as an array of sentences and we want to tokenize it.
const text = [
"I've gotta go. You'll find out in thirty years.",
"That's a great idea. I'd love to park.",
"What the hell is a gigawatt? Lorraine, are you up there?",
];
const sentenceToken = text.map((sentence) => sentence.split(" ")).flat();
console.dir(sentenceToken); // Output: [ "I've", "gotta", "go.", "You'll", "find", "out", "in", "thirty", "years.", "That's", … ]
NOTE. It's not a proper way to tokenize strings. It's a very simplified example to show how flatMap
works.
We map our text array and create an array of arrays containing single word tokens, then we flatten that array to get a one-dimensional array with all tokens. Simple, right? But do you know that we can do it better using flatMap
?
const text = [
"I've gotta go. You'll find out in thirty years.",
"That's a great idea. I'd love to park.",
"What the hell is a gigawatt? Lorraine, are you up there?",
];
const sentenceToken = text.flatMap((sentence) => sentence.split(" "));
console.dir(sentenceToken); // Output: [ "I've", "gotta", "go.", "You'll", "find", "out", "in", "thirty", "years.", "That's", … ]
It produces the same result, is a bit shorter, and is also slightly more performant.
The choice should be obvious.
Reducing arrays
Reducing is a process where an array is reduced to a single value, it is achieved by calling a reducer function on every element. A reducer function can take four arguments:
- Accumulator - it contains a value that is passed to every iteration, and after the final iteration, it becomes the value returned by
reduce
. - Current value - as the name says, it's the value of the current element.
- Current index - an array index of the current iteration.
- Source array - the array on which
reduce
is called.
Now some of you may wonder "ok, but where can I use this method?". Let's assume we have an array of numbers and we want to count the sum of its elements. It can be done using a for and adding every element of this array to a variable, but also it can be done using reduce.
Counting sum of array elements
const numbers = [77, 94, 668, 371, 2, 194, 54, 674, 7, 213, 26];
const sum = numbers.reduce((acc, value) => acc + value);
console.dir(sum); // Output: 2380
Reduce can also be used to find the minimal and maximal value in an array.
Finding minimum and maximum in an array
const numbers = [77, 94, 668, 371, 2, 194, 54, 674, 7, 213, 26];
const min = numbers.reduce((acc, value) => acc < value ? acc : value);
console.dir(min); // Output: 2
const numbers = [77, 94, 668, 371, 2, 194, 54, 674, 7, 213, 26];
const max = numbers.reduce((acc, value) => acc > value ? acc : value);
console.dir(max); // Output: 674
But hey! JavaScript has methods like min
and max
in its Math
object, can't we just use them? Of course, we can! Although, surprisingly, using reduce is faster. On a 10 000 elements array the result is as follows:
Let's check it also on a smaller array (with 10 elements).
Grouping objects in an array
Another very useful case for reduce
is grouping objects in an array by their properties. Let's take a look at this example:
const animals = [
{ name: "Dog", group: "mammals" },
{ name: "Eagle", group: "birds" },
{ name: "Tiger", group: "mammals" },
{ name: "Dolphin", group: "mammals" },
{ name: "Frog", group: "amphibians" },
{ name: "Parrot", group: "birds" },
];
const groupsSchema = {
mammals: [],
birds: [],
amphibians: [],
};
const groups = animals.reduce((acc, value) => {
acc[value.group].push(value);
return acc;
}, groupsSchema);
console.dir(groups);
In this example we got an array of animals, every animal has its name and the group it belongs to. Using reduce
we group them into separate arrays based on the value of group
. If you haven't noticed - we can pass an initial value for our accumulator by passing a second argument to reduce
.
Reducing backward?
reduce
is going to iterate from the lowest index to the highest (from start to end). However, sometimes we may need to reduce an array backward - in that case, we can use reduceRight
. It works identically to reduce, only the iteration starts from the highest index and goes to the lowest index.
const array = [[1, 2], [3, 4], [5, 6]];
const result1 = array.reduce((acc, value) => acc.concat(value));
const result2 = array.reduceRight((acc, value) => acc.concat(value));
console.dir(result1); // Output: [1, 2, 3, 4, 5, 6]
console.dir(result2); // Output: [5, 6, 3, 4, 1, 2]
Check if every element fulfils your condition
To check if all of the elements of an arrays are meeting our condition we can use every
. This method runs a test on every element. If everything will pass, then it returns true
- if not it returns false
.
const positives = [1, 56, 17, 592, -5, 9];
const isEveryPositive = positives.every((value) => value > 0);
console.dir(isEveryPositive); // Output: false
const positives = [1, 56, 17, 592, 5, 9];
const isEveryPositive = positives.every((value) => value > 0);
console.dir(isEveryPositive); // Output: true
NOTE. The test function also accepts a second argument aftervalue
, it's theindex
of the current element.
Check if any element fulfils your condition
When you want to check if one or more element pass your test, you can use some
. It's similar to every
, but it expects only some values to pass the test.
const positives = ["Hammer", "Screwdriver", null, "Wrench"];
const isSomeNull = positives.some((value) => value === null);
console.dir(isSomeNull); // Output: true
const positives = ["Hammer", "Screwdriver", "Pliers", "Wrench"];
const isSomeNull = positives.some((value) => value === null);
console.dir(isSomeNull); // Output: false
NOTE. Similar toevery
you can also access theindex
of the current element by passing a second argument to the test function.
Filtering arrays
Removing elements that don't fulfill our conditions can be pretty handy. filter
creates a new array consisting of elements that pass our test.
const numbers = [456, 1837, 123, 416, 12, 312, 7];
const filtered = numbers.filter((value) => value >= 100);
console.dir(filtered); // Output: [456, 1837, 123, 416, 312]
Removing duplicates from an array
const pets = ["Dog", "Cat", "Hamster", "Dog", "Canary"];
const filtered = pets.filter((value, index, array) => array.indexOf(value) === index);
console.dir(filtered); // Output: ["Dog", "Cat", "Hamster", "Canary"]
NOTE. this method is not performant on big arrays. With bigger arrays consider using the ES6 way to create unique arrays.
const pets = ["Dog", "Cat", "Hamster", "Dog", "Canary"];
const filtered = [...new Set(pets)];
console.dir(filtered); // Output: ["Dog", "Cat", "Hamster", "Canary"]
This will create a Set
from our array and then convert it back to a classic array. Sets are collections like array but they have a unique constraint - they don't accept duplicates and every value is unique. Below is a test comparing these methods on a 1000 elements array.
and on a small 20 elements array.
And that's all! We are almost at the end of this short series! The last part is going to cover searching in arrays. For now, take care! If you like my work consider signing up for my newsletter.