JavaScript Series: Adventures with Arrays | Reduce

We've learned about filter and map operations on JavaScript arrays already. Our possibilities do not end there, though.

Drawing conclusions

Often, once we mapped and filtered data, we'd like to draw some conclusions. Sometimes it's about taking a sum of all elements, sometimes it's about the average. Other times we'd like to accumulate results, e.g. count the number of occurrences.

What we're aiming to do is converting a collection into a single value. A value that represents our computation.

Reducing collections

Reducing is an operation that does exactly that with collections. It takes an initial value, current element and returns results of your computation. The next iteration starts with previously computed value and the next element. The process goes on until you run out of elements in the collection.

2b187026-6f93-4f74-b90e-e2444e59b8b6

The image above illustrates the process. Given an array of numbers 1, 2 and 3 we iterate over every element.

  • The process starts with an empty sum accumulator so we assign 0 to it. We add it to the first element and the result is 1
  • The next iteration starts with the previous sum which equals 1. We add it to the second element and the result is 3
  • In the final iteration, again we start with a previous sum accumulator which equals 3. We add the last number to it and the final result is 6

And that’s it! We just learned about reducing arrays!

Reducing in JavaScript

In theory this sound simple. Shall we be scared of JavaScript implementation? Not at all!

Like in the previous notes, we’re going to bring a collection of people data first.


  var people = [
    { gender: 'male', born: 1979 },
    { gender: 'female', born: 1976 },
    { gender: 'female', born: 1970 },
  ];

First exercise - count number of people born before 1975:

 
  function countBornBefore1975(sum, person) {
    if (person.born < 1975) {
      return sum + 1;
    }

    return sum;
  }

  
  // Count number of people born before 1975, start with 0
  people.reduce(countBornBefore1975, 0);
  // => 1

The number looks correct. How about counting number of female data records?


  function countFemaleRecords(sum, person) {
    if (person.gender === 'female') {
      return sum + 1;
    }

    return sum;
  }


  // Count number of female records, start with 0
  people.reduce(countFemaleRecords, 0);
  // => 2

Again, the number is correct!

How about solving a little bit more complex problem like getting a number of male and female records? Making two separate reduce operations sound like an option but there must be an easier way to achieve it. Right, there is!


  function countByGender(accumulator, person) {
    if (person.gender === 'male') {
      return {
        male:   accumulator.male + 1,
        female: accumulator.female,
      };
    }

    return {
      male:   accumulator.male,
      female: accumulator.female + 1,
    };
  }


  
  // Accumulate number of male and female records.
  // Use object literal. Start with zeros. 
  people.reduce(countByGender, { male: 0, female: 0 });
  // => { male: 1, female: 2 }

Perfect! As you can see, we can narrow things down not only to numbers, we can construct object literals and arrays out of initial collection as well!

You just learned about reducing arrays in JavaScript. You may perform some amazing computations when using it with map and filter. Full code listing to be found below.


  var people = [
    { gender: 'male', born: 1979 },
    { gender: 'female', born: 1976 },
    { gender: 'female', born: 1970 },
  ];



  function countBornBefore1975(sum, person) {
    if (person.born < 1975) {
      return sum + 1;
    }

    return sum;
  }

  // Count number of people born before 1975, start with 0
  people.reduce(countBornBefore1975, 0);
  // => 1



  function countFemaleRecords(sum, person) {
    if (person.gender === 'female') {
      return sum + 1;
    }

    return sum;
  }

  // Count number of female records, start with 0
  people.reduce(countFemaleRecords, 0);
  // => 2



  function countByGender(accumulator, person) {
    if (person.gender === 'male') {
      return {
        male:   accumulator.male + 1,
        female: accumulator.female,
      };
    }

    return {
      male:   accumulator.male,
      female: accumulator.female + 1,
    };
  }
  
  // Accumulate number of male and female records.
  // Use object literal. Start with zeros. 
  people.reduce(countByGender, { male: 0, female: 0 });
  // => { male: 1, female: 2 }

Now please go and try writing some reduce operations on your own, have fun!