Reduce method in practice - Part 2

In the last post I wrote about the reduce method in detail and had a look at some easy applications. In short, when we need to create one array or one object based on certain set of criteria, a good idea can be to think of reduce. Let’s have a look at some more complex examples.

Collecting elements based on a feature in common

We have actually more friends than that! They also did a favour for us and labelled their favourite movies with a category. We would like to know which movies belong to each category, regardless of the name of the friend. For example, if two of our friends like comedies, we want to have a comedy key with an array of comedies from both friends.

Here’s the list of our friends and their favourite movies:

const friends = [
  {
    name: 'Jill',
    movies: ['The Last Jedi', 'The Empire Strikes Back', 'A New Hope'],
    category: 'sci-fi'
  },
  {
    name: 'Jack',
    movies: ['Grown ups'],
    category: 'comedy'
  },
  {
    name: 'Jim',
    movies: ['The Bourne Identity', 'The Man from U.N.C.L.E.'],
    category: 'action'
  },
  {
    name: 'Jean',
    movies: ['The Pink Panther', 'Knocked Up'],
    category: 'comedy'
  },
  {
    name: 'Joe',
    movies: ['Mission Impossible'],
    category: 'action'
  }
];

First, let’s pause for a second and think. We need an object, where the keys are the categories and the values are the movies. As we want one object, we can think of applying reduce.

const createCategories = (moviesObj, friend) => {
  moviesObj[friend.category] = moviesObj[friend.category] ?
    [...moviesObj[friend.category], ...friend.movies] :
    friend.movies;
  return moviesObj;
};
const moviesByCategories = friends.reduce(createCategories, {});

The createCategories function does the work for us, so let’s see what’s happening there.

We know that we will need to return an object (moviesObj), and this object will be the accumulator of reduce. (That is, we collect the movies in this object.) First, we check if the category exists. If so, we concatenate the existing array of movies with the new matching one. If the category does not exist, we create one with the first occurrence of the category. The result will exactly be what we need:

// { 'sci-fi': [ 'The Last Jedi', 'The Empire Strikes Back', 'A New Hope' ],
//  comedy: [ 'Grown ups', 'The Pink Panther', 'Knocked Up' ],
//  action:
//   [ 'The Bourne Identity',
//     'The Man from U.N.C.L.E.',
//     'Mission Impossible' ] }

Use reduce on objects

In the last example we have an object of students who either passed or failed an exam. Our job is to collect the student in an object which has two properties, pass and fail. The values of these properties are an array of the names of students who passed or failed the exam, respectively.

Here is the list of the exam results:

const studentResults = {
  Adam: 'pass',
  Ben: 'pass',
  Charlie: 'fail',
  Daniel: 'pass',
  Emily: 'pass',
  Frank: 'fail'
};

Again, we need one final object, so we can give a chance to reduce. We will use the _.reduce lodash method here, as the native reduce can be used on arrays and we have an object of students here.

_.reduce accepts three arguments: the collection to iterate over (this will be the studentResults object in our case), the function, which will be applied on each property of the object (getStudentsByResult()) and the initial value of the accumulator, which will be an empty object here.

const getStudentsByResult = (resultsObj, value, key) => {
  (resultsObj[value] || (resultsObj[value] = [])).push(key);
  return resultsObj;
};
const examResults = _.reduce(studentResults, getStudentsByResult, {});

Let’s take a look at the getStudentsByResult() function. The callback function follows the same logic as the one in the native reduce method. We will use three of the four arguments. The only exception is that the third parameter will be the key instead the index, since we iterate through an object here.

First we check if the property (pass or fail with the value of array of students) exists in the final, to be returned object. If so, we push the name of the next student, which is one of the keys in the original studentResult object.

If the property does not exist, we define it as an empty array, and then push the first value in it.

The result will be exactly what we need:

// { pass: [ 'Adam', 'Ben', 'Daniel', 'Emily' ],
//  fail: [ 'Charlie', 'Frank' ] }

Conclusion

The reduce method – both native and lodash – can be used when one comes across a task when one collection (object or array) is needed. The main difference between these two approaches is that _.reduce can be applied on both arrays and objects, so it can be a good choice in bigger projects or when working in collaboration with others, where consistency might be important.

This concludes the last post in 2017. I wish everyone a happy and prosperous new year!