# Struggling with reduce(): Functional Programming: Use the reduce Method to Analyze Data

Are you sure that is all the code you have (minus the watchList array)? You have a return statement at the end, but I do not see a function it would be a part of.

Also, when using reduce, your callback function will typically require 2 argument. The first argument would be the accumulator and the second argument would be the element in the current iteration.

Whoops that was supposed to be a console.log() statement.

Thanks for pointing me in the right direction for the two arguments to reduce’s callback. I did some more reading and came up with this solution:

``````let averageRating = watchList.reduce((acc, val) => {
return val.Director == 'Christopher Nolan' ? acc + Number(val.imdbRating) : acc;
},0)

console.log(averageRating/4);
``````

I took a shortcut by manually counting the 4 Nolan movies and printing my result /4. Wasn’t sure how to reference the number of values that met the condition within my function.

Anyways, in my chrome console I’m getting the output 8.675 but I’m not passing the test. Any idea why?

Your console.log divides averageRating by 4, which is why it is showing 8.675. Th averageRating variable is the result before dividing by 4.

The tests are look at the value of averageRating and not your console.log statement.

Yes, part of the challenge is getting that 4 pragmatically instead of manually adding it to your expression. There are many different ways to get the value 4. One is to use the filter method to create a new array with only Christoper Nolan’s movies and then use reduce to sum the ratings and then divide by the filtered array’s length.

I was able to use a single reduce, but it was a little more complicated and debatable if it is more readable as what I describe above.

hey bro I just finished the code. It can be solved in a single line of code:

var averageRating = watchList.filter(x => x.Director === “Christopher Nolan”).map(x => Number(x.imdbRating)).reduce((x1, x2) => x1 + x2) / watchList.filter(x => x.Director === “Christopher Nolan”).length;

What you see here is just usage of the filter(), map() and reduce() methods. Hope you understand it!

1. First we filter the watchList array by director. This is the first part watchList.filter(x => x.Director === “Christopher Nolan”): this will return an array that only contains objects (x) that fit the condition where x.Director is equal to “Christopher Nolan”. The array we’re working with now is of length 4 and only contains objects with “Director” : “Christopher Nolan”

2. We now map the objects in the array to single values. Instead of having an array containing 4 objects with a lot of information we don’t need, we map it to single values (numbers, not strings) representing the imdbRating number. We use .map(x => Number(x.imdbRating)). Just read this as: "take each element (object) and return only the “imdbRating” value of that element BUT as a number and not a string.

3. Great! Now we have an array that looks like this: [8.8, 8.6, 9.0, 8.3]
Just use .reduce((x1, x2) => x1 + x2) to add them all up! What we see in the parentheses is just the notation for using reduce().

4. Now we need to get the average. We need to divide the number returned by .reduce() (a single number) by the length of the filtered array (after filtering the Christopher Nolan movies). We divide by watchList.filter(x => x.Director === “Christopher Nolan”).length;

And that’s it! solved in one line of code. Hope it helps

6 Likes

But can you do it with only one reduce? You are making 3 iterations through various arrays. It can be solved with one reduce which is only one iteration over the watchList array.

Your code has been blurred out to avoid spoiling a full working solution for other campers who may not yet want to see a complete solution. Thank you.

If he did, would that be “better”?

I’ve just finished the JS algorithms/data structures cert, and have been thinking about code quality as I try to evaluate my progress.

This was my solution to this challenge:

``````/*
What's the average IMDB rating for all of the Christopher Nolan Movies in the watchlist?
*/

// Find subset of movies directed by Christopher Nolan
let cNolanMovies = watchList.filter( item => item.Director == "Christopher Nolan" );

// Get the ratings for those movies
let movieRatings = cNolanMovies.map( item => Number(item.imdbRating) );

// calculate average
let averageRating = movieRatings.reduce( (total, sum) =>  total + sum )/movieRatings.length;
``````

I always tend to comment my code like this, and use these almost as pesudocode to help me as I’m coming up with the final solution. So as a result, my code tends to be longer overall and split into small chunks, but very clear and easy to comprehend.

A solution that was based on a single use of reduce() would be more efficient, but it would certainly also be more complex.

How do you make the decision on which side of this tradeoff to favor? Which strategy is more appropriate in a “professional” context?

3 Likes

Thanks a lot. It helped to filter for only movies directed by Nolan.

Thank you homie! This made a lot of sense. After some reading I filtered for only Nolan movies, and tried to use reduce on this new object by referencing object.imdbRating, but it’s way more comprehensible (at my skill level) to use map to create an array of ratings.

Thanks for responding, and especially for commenting out the logic behind each step. This was super helpful and I really appreciate it!!

A post was split to a new topic: Totally stuck on Use the reduce Method to Analyze Data

that was clearest explanation for me !! thanks

var i = watchList.filter(x => x.Director === “Christopher Nolan”);
var j = i.map(x => Number(x.imdbRating));
var k = j.reduce((x1,x2)=>x1+x2);

var averageRating = k / i.length;

console.log(averageRating);

1 Like

`var averageRating=watchList.filter(movies=>movies.Director==="Christopher Nolan").map(movies=>Number(movies.imdbRating)).reduce((sum, current)=>(sum+current),0)/4;`

How can I remove the 4 above and reference the length of array of numbers coming into reduce in the code above?
I know I can initialize another variable but I’m wondering if it’s possible to do it in this single line, not introducing too much difficulty of understanding the code later?

If you look at what the callback for `reduce` can be, it can have up to four arguments: the third is the current index, the fourth is the array you are iterating over.

Then you can either

1. divide by the array length as you go,
2. or you can check if you’re on the last index and divide by the length then.

However a. this will make the code complex and hard to read, b. if you divide as you go you’re gonna get the wrong result without some shenanigans to round numbers, which will complicate the code further (this is to do with floating point numbers).

Ideally, functions should do one thing and one thing well. As soon as you start to give them each ancillary jobs to do, it gets increasingly hard to debug the code. There is a reasonable reason for doing it in this case, but the increased difficulty of understanding your code outweighs any benefit imo.

(Btw you can do the entire operation in `reduce`, map and filter are just specialised versions of reduce anyway, and it’s fairly trivial to just move the logic from their callbacks into the `reduce`)

1 Like

Thanks for the code commenting and step by step. Really helped!

It’s a bit tricky because we need to capture the Cumulative moving average for each iteration if we want to avoid making extra variables.

``````let averageRating = watchList.filter( (movies) => {
return movies.Director === "Christopher Nolan";   // array of 'Nolan' movie objects.
}).reduce( (acc, currVal, currIndex, arr) => {
return acc + (+currVal.imdbRating - acc) / ( currIndex + 1);  // capture cumulative moving average
}, 0).toFixed(3);

console.log(averageRating);
``````

Hey Guys I tried on this quiz and I came up with this but always returns “NaN”
This is my code::

``````var filterMovies = watchList.filter(function(e) {
if (e.Director === "Christopher Nolan") {
return true;
}
});
var mappingItems = filterMovies.map(function(x) {
if (Number(x["imdbRating"]) === true) {
return true;
}
});
var averageRating = mappingItems.reduce(function(total, sum) {
return((total + sum) / mappingItems.length);
})

console.log(averageRating);
``````

can any body help me?

Here’s the solution for using `.reduce()` only to get the sum:

``````// Store name in CN for quick coding and testing
const CN = 'Christopher Nolan';
const averageRating = watchList.reduce((a, c) => {
if (typeof a === 'object' && a.Director === CN && c.Director === CN) {
return parseFloat(a.imdbRating) + parseFloat(c.imdbRating);
}
// Even if c.Director is not CN, we still need to return a for the next iteration evaluation
return c.Director === CN ? a + parseFloat(c.imdbRating) : a;
})
``````

This will only return the sum of all ratings, not the average. From this point, you can divide it by 4 if you manually count the movies by Christopher Nolan. Otherwise, you can use `.filter()` to get the length:

``````const length = watchList.filter(movie => movie.Director === 'Christopher Nolan').length;
``````

However, there’s a better way to avoid another loop. That is to return an array with the first element as the sum and the second one as the counter.

``````// Store name in CN for quick coding and testing
const CN = 'Christopher Nolan';

const sumAndCountArr = watchList.reduce((a, c) => {
// First if condition only evaluates to true the first time because 'a'
// will equal to an array in the second iteration

if (typeof a === 'object' && a.Director === CN && c.Director === CN) {
// The second element of the array is 2
// because a.Director and c.Director are both CN
// which means there are 2 movies by CN already at this point
return [parseFloat(a.imdbRating) + parseFloat(c.imdbRating), 2];
} else if (Array.isArray(a) && c.Director === CN) {
// From the second iteration on, 'a' will be an array
// And the second if condition will be true from this point
// We need to check it c.Director is CN or not
// If so we mutate the array 'a' to update new rating sum
// and count of movies by CN

// You can clone array a to avoid mutation
// but I don't think it's necessary
a = [a[0] + parseFloat(c.imdbRating), a[1] + 1];
}

// Finally we returned the mutated array 'a'
// What you need to know is that though the second condition is false
// in some cases and 'a' is not mutated, 'a' still needs to be returned
// So in the next iteration we have 'a' to keep evaluate the if conditions
// Try put a in the else if and you'll see what I mean
// I've made many mistakes at this point, so warn you is necessary.
return a;
});

const averageRating = sumAndCountArr[0] / sumAndCountArr[1];
``````

Great explanation! I am still confused about the x1 and x2. in step 3.