10 Essential JavaScript Utility Functions Made with Reduce
As a JavaScript developer, you‘re likely familiar with built-in array methods like map(), filter(), and reduce(). Of these, reduce() is perhaps the most powerful and versatile. With reduce(), you can transform arrays in complex ways with just a few lines of code.
In this article, we‘ll leverage the power of reduce() to create 10 super handy utility functions. These functions cover common operations like summing numbers, flattening arrays, and grouping elements. Once you have these in your toolkit, you‘ll reach for them constantly!
But first, let‘s make sure we‘re on the same page about how reduce() works. The reduce() method iterates through an array and transforms it into a single value. It takes a callback function that specifies how to combine each element with the running "accumulator" value. It also takes an initial value to use as the first accumulator.
Here‘s the basic syntax:
const result = array.reduce((accumulator, element) => {
// Combine element with accumulator and return new accumulator
}, initialValue);
Alright, now that we‘ve covered the basics of reduce(), let‘s dive into our 10 utility functions!
1. sum
First up is a function to calculate the sum of an array of numbers. This is a quintessential use case for reduce().
const sum = (numbers) => numbers.reduce((total, n) => total + n, 0);
We set the initial accumulator to 0. Then for each element, we simply add it to the running total.
Here‘s how you would use it:
sum([1, 2, 3, 4, 5]); // => 15
sum([]); // => 0
sum([-2, 0, 2]); // => 0
2. average
Calculating an average is very similar to sum, we just need to divide the total by the number of elements at the end:
const average = (numbers) => numbers.reduce((total, n, index, array) => {
total += n;
return index === array.length - 1 ? total / array.length : total;
}, 0);
The callback function gets passed a few more arguments we can use: the current index and the original array. We can check if we‘re at the last element and return the final average if so.
Example usage:
average([1, 2, 3, 4, 5]); // => 3
average([-1, 0, 1]); // => 0
average([100]); // => 100
3. min
To find the minimum value in an array, we can use reduce() to compare each element to the running minimum:
const min = (numbers) => numbers.reduce((min, n) => n < min ? n : min);
No initial value is specified, so the accumulator starts as the first element. Then we just return the smaller of the accumulator and the current element.
Examples:
min([1, 2, 3]); // => 1
min([5, 0, -2, 5]); // => -2
min([-10]); // => -10
4. max
Finding the maximum value works the same way as min, just flip the comparison:
const max = (numbers) => numbers.reduce((max, n) => n > max ? n : max);
And some examples:
max([1, 2, 3]); // => 3
max([5, 0, -2, 5]); // => 5
max([-10]); // => -10
5. pluck
Often we have an array of objects and want to extract an array of particular property values. We can do this by "plucking" the values with reduce():
const pluck = (array, key) => array.reduce((values, obj) => {
values.push(obj[key]);
return values;
}, []);
The accumulator starts as an empty array. For each object, we extract the value of the specified property key and push it into the accumulator array.
Here‘s how we can use it:
const people = [
{ name: "Alice", age: 21 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 30 }
];
pluck(people, ‘name‘); // => ["Alice", "Bob", "Charlie"]
pluck(people, ‘age‘); // => [21, 25, 30]
6. flatten
Flattening nested arrays is a common task that reduce() makes easy. Here‘s one way to implement it:
const flatten = (arrays) => arrays.reduce((flat, array) => flat.concat(array), []);
We initialize the accumulator as an empty array. For each subarray, we concatenate its elements to the accumulator array, gradually building a single flattened array.
Example usage:
flatten([[1, 2], [3, 4], [5]]); // => [1, 2, 3, 4, 5]
flatten([[1], [], [2, 3]]); // => [1, 2, 3]
flatten([]); // => []
7. countBy
The countBy function takes an array and counts the occurrences of each value (or of a property‘s values if given an array of objects). Here‘s how to implement it with reduce():
const countBy = (array, key) => array.reduce((counts, element) => {
const value = key ? element[key] : element;
counts[value] = (counts[value] || 0) + 1;
return counts;
}, {});
The accumulator is an object that will hold the counts. The keys are the values we‘re counting and the values are the counts. For each element, we either use the element itself as the key (if no property key is specified), or we use the value of the specified property.
We use the logical OR operator to either increment an existing count or initialize a new one at 1.
Some examples:
countBy([1, 1, 2, 3, 3, 3]); // => { ‘1‘: 2, ‘2‘: 1, ‘3‘: 3 }
const pets = [
{ type: "dog", name: "Spot" },
{ type: "cat", name: "Fluffy" },
{ type: "dog", name: "Rover" },
{ type: "cat", name: "Whiskers" }
];
countBy(pets, ‘type‘); // => { ‘dog‘: 2, ‘cat‘: 2 }
8. groupBy
Grouping elements into categories is a similar concept to counting, but instead of tracking counts, we build arrays of elements for each category. The implementation is also quite similar:
const groupBy = (array, key) => array.reduce((groups, element) => {
const value = key ? element[key] : element;
groups[value] = (groups[value] || []).concat(element);
return groups;
}, {});
The accumulator is again an object, but this time the values are arrays representing the groups of elements. For each element, we retrieve the existing group (or initialize a new empty array) and concatenate the element to it.
Here‘s how we can use groupBy:
groupBy([1.1, 2.3, 1.5, 2.8], Math.floor); // => { ‘1‘: [1.1, 1.5], ‘2‘: [2.3, 2.8] }
const students = [
{ name: "Alice", major: "Computer Science", gpa: 3.5 },
{ name: "Bob", major: "Art", gpa: 3.2 },
{ name: "Charlie", major: "Computer Science", gpa: 3.8 },
{ name: "Diane", major: "Art", gpa: 3.9 },
{ name: "Evan", major: "Physics", gpa: 3.4 }
];
groupBy(students, ‘major‘);
// =>
// {
// ‘Computer Science‘: [
// { name: "Alice", major: "Computer Science", gpa: 3.5 },
// { name: "Charlie", major: "Computer Science", gpa: 3.8 }
// ],
// ‘Art‘: [
// { name: "Bob", major: "Art", gpa: 3.2 },
// { name: "Diane", major: "Art", gpa: 3.9 }
// ],
// ‘Physics‘: [
// { name: "Evan", major: "Physics", gpa: 3.4 }
// ]
// }
9. partition
Sometimes we want to split an array based on a condition. Elements that match the condition go into one array, the rest go into another. We can use reduce() to build both arrays simultaneously:
const partition = (array, predicate) => array.reduce((result, element) => {
result[predicate(element) ? 0 : 1].push(element);
return result;
}, [[], []]);
The accumulator is an array containing two subarrays, one for elements matching the predicate and one for elements that don‘t. For each element, we use the predicate function to determine which subarray to push it into (using a ternary operator to select the subarray index).
Some usage examples:
partition([1, 2, 3, 4, 5], n => n % 2 === 0); // => [[2, 4], [1, 3, 5]]
const users = [
{ name: "Alice", isActive: true },
{ name: "Bob", isActive: false },
{ name: "Charlie", isActive: true },
{ name: "Diane", isActive: false }
];
partition(users, user => user.isActive);
// =>
// [
// [{ name: "Alice", isActive: true }, { name: "Charlie", isActive: true }],
// [{ name: "Bob", isActive: false }, { name: "Diane", isActive: false }]
// ]
10. zip
Our last utility function is zip, which "zips" multiple arrays together into an array of tuples. Each tuple contains elements at the same index from the input arrays.
Here‘s the reduction:
const zip = (...arrays) => arrays[0].reduce((zipped, _, index) => {
zipped.push(arrays.map(array => array[index]));
return zipped;
}, []);
The accumulator starts as an empty array that will contain the tuples. We iterate over the first input array, using its indexes to pluck the corresponding elements from each array using map(), and push the resulting tuple into the accumulator.
Example usage:
zip([1, 2, 3], [‘a‘, ‘b‘, ‘c‘]); // => [[1, ‘a‘], [2, ‘b‘], [3, ‘c‘]]
const names = ["Alice", "Bob", "Charlie"];
const ages = [21, 25, 30];
const locations = ["New York", "London", "Paris"];
zip(names, ages, locations);
// =>
// [
// ["Alice", 21, "New York"],
// ["Bob", 25, "London"],
// ["Charlie", 30, "Paris"]
// ]
Conclusion
And there you have it — 10 insanely useful utility functions, all implemented with the magic of reduce()! I hope diving into these implementations has given you a better understanding of the power and versatility of reduce().
With these utilities in your pocket, you‘ll be able to write cleaner, more concise code. You‘ll be iterating over arrays like a pro!
But beyond just using these specific functions, I encourage you to look for opportunities to use reduce() in your own code. Whenever you need to transform an array into a single value (whether that‘s a number, string, object, or another array), consider if reduce() could help you write more elegant code.
Happy coding!