16
.
4
.
2019

One reduce() to rule them all

Reduce? Do I make stuff smaller? Less… er?

Well… Yes and no.

The idea behind reduce is a simple, but a very powerful, one. It allows you to take an array of data and transform it into almost anything that you can imagine.

MDN defines reduce as:

The reduce() method executes a reducer function (that you provide) on each member of the array resulting in a single output value.

That’s a nice definition which may lead you to believe that you always have to produce one value, like a number or a string. While technically true, you can still produce another array (which holds many values) or an object (as complex as you want it) or an array of objects. That means that you can reduce an array of 3 strings to one number or to an array of 200 very complex objects. Now, if that isn’t powerful, I don’t know what is.

How does it work?

reduce is like one of those games where you can grasp the rules in an hour or two but still discover new ways of having fun for years to come.

In itself, the inner workings of reduce are simple.
An abstract recipe would be:

  1. take an array of values,
  2. add a function which will run on every element,
  3. make sure that the function returns a value,
  4. use that returned value when running on the next element,
  5. add a pinch of salt,
  6. accept the return of the function at the last element as your final value.

In short — you are building up to the final value by always having the result of the function running on the previous element.

Here is an example of building up an object from an array of values:

As you can see, we start building up the object immediately and keep changing values as we are working through the array. Don’t worry if you don’t know how it happens yet, the important message is that you are always carrying your result and modifying it as you are working through your values.

Starting small

I hope that I could sell you on the idea of how powerful reduce is. The logical next step would be to start small and build up your knowledge of reduce. I hope that by the end of this article, you’ll be able to solve a wide range of problems in an elegant fashion.

Higher-order functions

There is one more concept that we need to get out of the way before digging into reducing all the things and that’s the concept of higher-order functions.

Wikipedia defines them as:

In mathematics and computer science, a higher-order function is a function that does at least one of the following:
- takes one or more functions as arguments (i.e. procedural parameters),
- returns a function as its result.

The first point is the one we’re interested in. reduce is a higher-order function because it takes a function as an argument — exactly the function we’ve already mentioned, the one which is going to get applied to each element.

Start reducing

The basic reduce function is easy to define:

myArray.reduce(callbackFunction, initialValue)

Basically, this is going to take an array and run the given callback function over all the elements in the array. Each function call (each step) will deliver a returning value. This returning value is going to get passed into the next step.

You may rightfully ask: what about the first element of the array? Where is the “result” from the previous step? That’s a very good question! That’s why reduce accepts another parameter — initialValue. We can hard code what the initial value is going to be for the first element, as if it was a return from a previous element. For example: if we were to write a function which would sum up all the numbers in an array (spoiler: which we are going to do), a natural initial value would be a zero because it’s a great starting point for addition.

Highly functional

With the basic reduce defined, let’s talk about callbackFunction. The callback function, which is going to get called for each element and will receive the result from the previous call looks like this:

const callbackFunction = function(
   accumulator,
   currentValue,
   currentIndex,
   array) { ... }

Although this seems a little intimidating at first, it’s actually quite easy to grasp:

  • accumulator represents the returned value from the previous function call. If we’re at the first element, you’ve probably guessed it, it’s going to contain initialValue.
  • currentValue is the current element of the array upon which the function is being called.
  • currentIndex represents the index of the current element.
  • array is the array which we are reducing right now.

Most of the time, you’ll be interested in the accumulator and currentValuebecause they’ll allow you to build up your final result. accumulator and array are nice to haves to work with even more complex reducing logic (where you need to know how much of the array you’ve already traversed).

If you ever need a refresher on what each part of a reducer does, here’s a handy image:

Our first reducer — reducing an array to one value

Let’s write a function which is going to sum together all numbers in an array if used in a reduce function.

github:2cdc1507cebf08e1d1cf949692ac82d3

This might seem like a lot of code, but most of the time you wouldn’t be writing it this way. The whole reduce function (with the callback function and initial value) would be only 4 lines of code.

Now, how does this work?

If you can imagine it as a workflow, it’s really easy:

  1. The first element features the initial value (0) as the accumulator and the current element is 100 (the first element). They get added together (0 + 100 = 100) and returned.
  2. The accumulator for the second element is the returned value from calling the function on the first element (100). The current (second) element is 200. They get added together (100 + 200 = 300) and returned to be the accumulator for the next element.
  3. The third element gets an accumulator of 300 (return value from the previous step). The current element is 300. When we add them together (300 + 300 = 600), we return the result and, since there are no more elements, 600 gets accepted as the final value.

This might seem trivial, but that is exactly the way reduce works, even for more complicated cases.

A couple of points here:

  • The callback function only has the accumulator and current value as parameters — that’s because the rest isn’t interesting for this case.
  • The chosen initial value is zero because zero is a neutral element in addition and therefore a perfect starting point to start summing up our numbers.

The next challenge — changing each element of an array

Let’s say that we have an array of objects representing people and we’ve noticed that all of their names have a lowercase first letter. That simply won’t do.

This is how a potential fix would look like:

github:874ca885b2836aaf1e7cfcf8763c8364

Phew. Lots of things going on here. Luckily, the logic of reduce remains the same:

  1. The first element gets into the function (michael Scott) and the initial value is an empty array. That’s because we’ll be filling up that array with corrected people and returning that ever growing array into the next step. The person’s first name gets corrected (michael → Michael) and the corrected person gets added to the array (which now consists of 1 element: Michael Scott).
  2. The second step gets the accumulator from the previous step (which is an array with 1 person) and the current element (which is jim Halpert). The person gets fixed (jim → Jim) and added to the array (which now contains 2 people: Michael and Jim). That array gets returned so that it becomes the accumulator for the third element.
  3. The third step gets the accumulator with 2 people (Michael and Jim) and the current (third) element (dwight). Dwight gets fixed and we return an array with all 3 elements (Michael, Jim and Dwight — probably off to do a prank). Since this is our last element, this is also going to be the final value.

We can also try to visualise it:

Let’s get meta

Up until now, you’ve seen how to work with arrays of different data and how to produce different results based on that data. But, what about creating something truly special? What about creating a generic function which would do the same as the map() function?

Let’s try it and see what happens.

github:4825c6ff3bd367f33d8724e59673c8d8

We’ve created a new function called mapFunction(). This function is a generic map function which will go through all elements of an array and apply a given function to each and every one of them and then return the new array. It’s the exact same thing the built-in map() function would do.

This little exercise is cool because it lets you think on an even deeper level and you produce a function that’s limitless in its functionality. If you want an other way of changing each value of an array, write a new function which should be applied like the addTheGreat() function. Let’s say that you want to change all the strings in an array to uppercase. It would be trivial:

const upperAll = function(element) { return element.toUpperCase(); }

After that, call our mapFunction with the new function:

const upperPeople = mapFunction(people, upperAll);

Some more exercise?

If you want to try out even more stuff, you can try implementing the filter function which removes elements from an array based on the return value (true or false) of the entering function.

Reduce all the things

In conclusion, reduce will open up a whole new world of working with data for you if you try to tackle a couple of problems with it. It’s unbelievable how elegantly you can solve something which seemed like a daunting task using for loops.

This article is an introduction and is meant to whet your appetite for more. The good thing is, there are loads of great articles, talks and videos about awesome things you can do with reduce.

Invest into data transformation using reduce and you’ll get so much value out of it.

But wait, what about the initial example? 😮

Nicely noticed — we didn’t implement the initial example (the one with maximum, minimum and average). That one would be a great exercise for you and here is a possible solution for it:

github:ca5b886b349a616b53dde5f0ca52d71f

Kristian
Agile UX/UI Engineer

Leidenschaft, Freundschaft, Ehrlichkeit, Neugier. Du fühlst Dich angesprochen? Dann brauchen wir genau Dich.

Bewirb dich jetzt!