How to: array reduce in JavaScript and PHP
If you make websites, chances are that you work with arrays. A lot. They’re everywhere—a list of posts, a list of followers, a list of links. But working with arrays can sometimes feel a little cumbersome. Sometimes it can take more code than it seems like it should to process an array. Sometimes it can feel… hacky.
You may have heard other developers say, “Just use a reducer.” Or, “a reducer could do that!” Or maybe even, “Reduce is cool and you have to be a functional programming genius like me to understand it.”
Not true, I say! I know next to nothing about functional programming (so far). But I was able to understand and start using reduce functions in my own work. And so can you!
Arrays, the conventional way
Before we look at the power of the reduce function, let’s look at some of the other ways you might access an array. In JavaScript, for example, you are probably familiar with something like:
1var fruits = ['orange', 'apple', 'banana'];2for(let i = 0; i < fruits.length; i++) {3 // do something with fruits[i]4}
The above code shows a for
loop that is being used to iterate over each element in the fruits array. Really, all it is doing is incrementing a counter, which you can then use to access your array.
You might even be aware of the nicer way of doing this, using an array method:
1var fruits = ['orange', 'apple', 'banana'];2fruits.forEach(fruit => {3 // do something with fruit4});
The forEach
method accepts a callback function, and runs that function over each element in the array.
This is the first important point to understanding a reducer. The idea of writing a callback function that gets run for each element in the array. Each time it runs, it takes an array element as its argument. So just hang on to that for a second.
List operations
Here’s another functional programming term for you. List operations just refers to methods that run against arrays (lists). Most of the time, they iterate over the array, do things, and return another array as the result.
As a quick example, take the filter
function:
1var fruits = ['orange', 'apple', 'banana'];2var fruitsILike = fruits.filter(fruit => fruit === 'banana');
The filter
function runs a callback function over each element in the array. The callback function must return true
or false
, depending on whether you want the current element to be included in the final array. What I just called the “final array” is what is returned by filter
—it’s a new array containing all of the elements for which you returned true
. In this case, an array containing just one element, banana
.
Another useful list operation is the map. Let’s look at this one in PHP.
1$fruits = [ 'orange', 'apple', 'banana' ];2$fruit_initials = array_map( function( $fruit ) {3 return substr( $fruit, 0, 1 );4}, $fruits );5 6// Expected return value: [ 'o', 'a', 'b' ];
The array_map
function iterates over each element in the array. It returns a new array with the same number of elements as the original, but with each element being whatever you chose to return. Each element in the old array maps to an element in the new one.
Enter the reducer
Now that we have looked at other list operations, we have the basis for understanding the reducer. Before I show you a code sample, consider the following metaphor.
Imagine you have a piece of paper with a list of numbers. You have a calculator which you use to add up all the numbers. As you are adding up the numbers, you have a running total. As you progress down your list of numbers, you simply keep adding each number to the running total.
This is very similar to how a reducer works. You have a list of things (the array) that you are iterating over. As you do, you have access to a variable that represents the current element. Just like we have seen earlier. But you also have a variable that represents the “running total,” if you will. We’ll call it, “carry.” Let’s take a look at this in JavaScript:
1var numbers = [42, 7, 99, 2, 33];2var total = numbers.reduce((carry, num) => carry + num, 0);
In the code above, we are supplying reduce
two arguments:
- Our callback function, which should return the new running total at the end of each iteration.
- The initial value of our running total, which in this case we want to be
0
.
Okay, so we’re iterating over the numbers
array. The reduce
function expects our callback function to return the new running total for each iteration. That’s the carry + num
bit[1]. If we were to console.log()
the value of carry
at the beginning of each iteration, it would look like this:
04249148150
And the final total would be 183.
Here’s the same reducer in PHP:
1$numbers = [42, 7, 99, 2, 33];2 3$total = array_reduce( $numbers, function($carry, $num) {4 return $carry + $num;5}, 0 );
So I can add some numbers together? Big deal…
Okay, now this is where reducers get their reputation for being all-powerful. We’ve been thinking of the carry value as being a running total. But here’s the thing—your callback function can return any type of value, and whatever value you return will be passed to your callback function on the next iteration.
Let’s look at a contrived example to see how you can use a reducer for something that has nothing to do with adding numbers together.
Let’s say we have one array that contains fruits and vegetables. Instead we would like to have an object that looks like this:
1{2 fruits: [], // array containing only fruits3 veggies: [] // array containing only veggies4}
This is how we could do it with a reducer:
1var food = ['apple', 'okra', 'banana', 'spinach']; 2 3var organizedFood = food.reduce((carry, item) => { 4 if(item === 'apple' || item === 'banana') { 5 carry.fruits.push(item); 6 } else { 7 carry.veggies.push(item); 8 } 9 10 return carry;11}, { fruits: [], veggies: [] });
Let’s set aside the fact that this function doesn’t scale very well (it can’t handle any other fruits except apples and bananas). It does show how we can use a reducer to keep a running value of any type. Let’s step through the code.
We pass reduce
our callback function. We also pass it the initial value. In our case, we want the initial value to be an empty object with the properties fruits
and veggies
. We set the value of both of those properties to an empty array.
Our callback function looks at each element in the array. If it matches one of the fruits, we push that element to the carry.fruits
array. If it doesn’t, we assume that it is a vegetable and we push it to the carry.veggies
array. Then, critically, we return the object after we’ve modified it. That ensures that we save our updated running value.
Once we get to the end of the function, organizedFood
should look like this:
1{2 fruits: [ 'apple', 'banana' ],3 veggies: [ 'okra', 'spinach' ]4}
And here’s the exact same reducer in PHP:
1$food = ['apple', 'okra', 'banana', 'spinach']; 2 3$organizedFood = array_reduce( $food, function( $carry, $item ) { 4 if( $item === 'apple' || $item === 'banana' ) { 5 array_push( $carry['fruits'], $item ); 6 } else { 7 array_push( $carry['veggies'], $item ); 8 } 9 10 return $carry;11}, [ 'fruits' => [], 'veggies' => [] ] );
Again, the secret sauce here is the fact that we have this running value, $carry
, that can be anything we want. And we have access to it on each iteration of our loop. On the last iteration, whatever we return as the $carry
value will be what is returned by the array_reduce
function.
Reducer redux
I hope this article has helped to demystify the reducer function and how it can be used for all sorts of things. In summary, a reducer function works like this:
- It iterates over list of things, running your callback function.
- It receives an initial running value, which can be anything.
- For each thing in the list, your callback has a chance to modify/replace the running value.
- After passing over the whole list, the reducer function returns the running value.
In JavaScript, you can call .reduce()
directly on an array. In PHP, you use the array_reduce()
function.
The next time you find yourself about to use a for
loop for the purpose of looping over an array and keeping up with some other arbitrary value as you go, it’s almost certainly a great time to consider using a reducer.