DEV Community

Cover image for Reduce's many uses
Caleb Weeks
Caleb Weeks

Posted on

 

Reduce's many uses

The reduce array method is often introduced along with map and filter, but it is such a powerful method that I felt it deserved a post of its own. The traditional example used to introduce reduce is the following function that will calculate the sum of all the elements in an array:

const array = [1, 2, 3, 4, 5];
const sum = array.reduce((a, b) => a + b);
Enter fullscreen mode Exit fullscreen mode

From this example, you might start developing an intuition that this method reduces the elements in the array down to a single value, and it certainly can and does in many cases. However, since a value can be pretty much anything in JavaScript, the reduced result may not necessarily be a single primitive value or even smaller that the original array (if you can come up with some notion of size to compare them).

Here is the abstraction that reduce provides:

const array = [1, 2, 3, 4, 5];
const INITIAL_VALUE = 0;

const reduceFunction = (accumulator, element) => accumulator + element;

// Without reduce
let accumulator = INITIAL_VALUE;
for (let i = 0; i < array.length; i++) {
  accumulator = reduceFunction(accumulator, array[i])
}

// With reduce
const accumulator = arrray.reduce(reduceFunction, INITIAL_VALUE);
Enter fullscreen mode Exit fullscreen mode

The reduceFunction, also known as the reducer, takes two values and returns a value of the same type as the first argument. This returned value is supplied as the first argument of the next iteration. If no initial value is given, the first element in the array will be used as the initial value. The implementation of the reduce method on the array prototype makes it an instance of a Foldable, and Haskell calls this function foldl (for fold from the left). Let's take a look at some things reduce can do!

Map

You can use reduce to replace map. The benefits of this approach are not immediately obvious, but it will be helpful when we cover transducers in the future.

const array = [1, 2, 3, 4, 5];
const mapFunc = (number) => number * 2;

// With map
const newarray = array.map(mapFunc);

// With reduce
const mapReducer = (func) => (accumulator, element) =>
  [...accumulator, func(element)];
const newarray = array.reduce(mapReducer(mapFunc), []);
Enter fullscreen mode Exit fullscreen mode

Filter

You can use reduce to replace filter as well, and this will also be helpful when we talk about transducers.

const array = [1, 2, 3, 4, 5];
const predicate = (number) => number % 2 === 0;

// With filter
const newarray = array.filter(predicate);

// With reduce
const filterReducer = (predicate) => (accumulator, element) =>
  predicate(element) ? [...accumulator, element] : accumulator;
const newarray = array.reduce(filterReducer(predicate), []);
Enter fullscreen mode Exit fullscreen mode

Various aggregates

Pretty much anything that you could think of creating from an array can be created using reduce. I particularly like this implementation of creating the upper triangular matrix of an array. The reduce function takes an optional third argument which is the index of the element. (It also takes a fourth optional argument which is the array itself).

// Using nested for loops
const upperTriangle = (arr) => {
  let triangle = [];
  for (let first = 0; first < arr.length; first++) {
    for (let second = first + 1; second < arr.length; second++) {
      triangle.push([arr[first], arr[second]]);
    }
  }
  return triangle;
};

// Using reduce and map
const upperTriangle = (arr) =>
  arr.reduce((triangle, first, i) => {
    const rest = arr.slice(i + 1);
    const pairs = rest.map(second => [first, second]);
    return [triangle, pairs].flat();
  }, []);
Enter fullscreen mode Exit fullscreen mode

Function composition

You read that right. You can implement function composition with reduce!

const toWords = (string) => string.split(" ");
const count = (array) => array.length;
const wpm = (wordCount) => wordCount * 80;

const speed = (string) =>
  [toWords, count, wpm]
  .reduce((composed, fn) => fn(composed), string);
Enter fullscreen mode Exit fullscreen mode

Recursive functions

If you can convert a recursive function to an iterative approach, you can also implement it using reduce. Recursive functions are often used because of their semantic definitions, but using reduce does not have the issue of potentially filling up the function call stack while enabling declarative definitions if done well.

const factorial = (number) =>
  number === 0 ? 1 : number * factorial(number - 1);

const factorial = (number) =>
  Array(number)
    .fill(number)
    .reduce((acc, elem, i) => acc * (elem - i));
Enter fullscreen mode Exit fullscreen mode

Sum and friends

Let's revisit the sum function that we started with. It turns out that there are a bunch of examples that follow a similar pattern:

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((a, b) => a + b, 0);
const product = numbers.reduce((a, b) => a * b, 1);
const min = numbers.reduce((a, b) => (a < b ? a : b), Infinity);
const max = numbers.reduce((a, b) => (a > b ? a : b), -Infinity);

const booleans = [true, false, false, true];
const any = booleans.reduce((a, b) => a || b, false);
const all = booleans.reduce((a, b) => a && b, true);
Enter fullscreen mode Exit fullscreen mode

In all of these cases, the initial value can be left out, but I included them for clarity. All of these reducers take two elements of the same type and return another thing of the same type. This property combined with appropriate starting values (known as identities) forms the definition of a Monoid. In the next post, we will take a closer look at Monoids and the various places they come up in programming.

Hopefully this post has given you a better intuition for the uses of reduce. Combined with map and filter, I seldom find myself writing a for or while loop anymore. The imperative loops are more helpful if you have to do something a certain number of times, but as we'll see soon, it is better to work with expressions of values than simple statements.

Top comments (14)

Collapse
 
lukeshiru profile image
Luke Shiru • Edited

I love the functional style in JavaScript, yet I always recommend to avoid Array.prototype.reduce when we have a more idiomatic way of doing the same. So from the list on your post:

  • Map: Just use Array.prototype.map. It's faster and more readable.
  • Filter: Just use Array.prototype.filter. It's also faster and more readable.
  • Various aggregates: Use Array.prototype.flatMap and Array.prototype.map like this:
const upperTriangleMap = array =>
    array.flatMap((first, index) =>
        array.slice(index + 1).map(second => [first, second]),
    );
Enter fullscreen mode Exit fullscreen mode

It's also faster and more readable. The nested fors are even faster, but readability is compromised, so I wouldn't recommend it.

  • Function composition: This is actually a good use-case for reduce, but keep in mind that soon we'll get the pipe operator (|>) and that will make it even easier:
const speed = string => string |> toWords(%) |> count(%) |> wpm(%);
Enter fullscreen mode Exit fullscreen mode
  • Recursive functions: You don't need to fill the Array, you can just reduce it:
const factorial = number =>
    number < 2
        ? 1
        : Array.from(Array(number)).reduce(
                (total = 1, _, index) => total * (index + 1),
          );
Enter fullscreen mode Exit fullscreen mode

This one is also faster.

  • Sum and friends: Definitely the best use case for reduce is sums and products, yet for min, max, any and all you could just:
const min = array => Math.min(...array);
const max = array => Math.max(...array);
const any = array => array.some(Boolean);
const all = array => array.every(Boolean);
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
 
sethcalebweeks profile image
Caleb Weeks • Edited

Wow! What a great response! Here are some more thoughts in no particular order:

  • I love the upper triangle simplification and might just steal it for a project I'm working on.
  • When I tried reducing on a new array that wasn't filled, it did not iterate over it. I believe it's because creating a new array sets the length, but creates a sparse array where no indexes are defined.
  • Folding over Monoids is definitely where I think reduce really shines, so I left that as the punchline.
  • Reduce is very versatile, but I agree that there are methods that simplify certain functionalities even further. I usually find myself using reduce first if another method isn't immediately obvious, then looking for ways to refactor.
Collapse
 
t0nyba11 profile image
Tony B

Nice article, although I wouldn't recommend using reduce() for any() and all(), as it will iterate the entire array without exiting. Best to use methods like every() and some() on Arrays (for example)

Collapse
 
sethcalebweeks profile image
Caleb Weeks

Thanks for the suggestion! The built in every and some is definitely the way to go!

 
sethcalebweeks profile image
Caleb Weeks

If you've checked out some of my other posts, perhaps you've come across this one, where I explain why I think functional programming is worth learning. Anything worth learning should challenge what you already know or how you already think.

What about the upper triangle implementation is confusing? Do you prefer the nested for loop approach?

Collapse
 
sethcalebweeks profile image
Caleb Weeks

Do you have a problem with reduce in particular, or just functional programming in general? Could you suggest some alternatives to some of the problems that reduce solves?

I would be the first to admit that functional programming can seem inaccessible and impractical, so this is my attempt to explain the benefits of the paradigm.

In every area of life, it is wise to have a balance of creativity grounded in principles discovered by people who have come before.

Collapse
 
jonrandy profile image
Jon Randy πŸŽ–οΈ

Typo in article title?

Collapse
 
sethcalebweeks profile image
Caleb Weeks

I love your Metho library, by the way! I haven't found a good use for it in my applications yet, but the implementation is really creative.

Collapse
 
sethcalebweeks profile image
Caleb Weeks

It took me way longer than I care to admit to figure out what the typo was... Thanks for catching it!

Collapse
 
mthompsoncode profile image
Mark Thompson

I always avoid reduce and try to use any other es6 Array methods. But because of that, i don't have a lot of experience with reduce.

Nice post! Glad I could learn more about reduce

Collapse
 
sethcalebweeks profile image
Caleb Weeks

I like to think of reduce as the method that can do pretty much everything, but there are often methods that simplify certain functionalities even further such as map and filter. But if you wanted to map and filter during the same iteration, so example, reduce provides the necessary versatility.

πŸ› See a bug on this page?

Join our team and help us fix it. We're hiring for a Senior Full Stack Engineer β€” Head here to learn more and apply.