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

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

#1

Tell us what’s happening:
I’m having trouble using reduce to filter out only movies directed by Christopher Nolan.

watchList[0]["Director"] is returning returning the director as a string (which is what I want), but this is not behaving the way I intended in the reduce function.

Can someone point me in the right direction?

Your code so far

const reducer = function(x){
  return watchList[x]["Director"]=="Christopher Nolan";
}
var nolan = watchList.reduce(reducer);
return nolan;

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36.

Link to the challenge:
https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/functional-programming/use-the-reduce-method-to-analyze-data/


#2

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.


#3

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.


#4

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)

// Add your code above this line
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?

Thanks for your help!


#5

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.


#6

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

// Add your code below this line

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;

// Add your code above this line
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


#7

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.


#8

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.


#9

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?


#10

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


#11

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!!


#12

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


#13

that was clearest explanation for me !! thanks

// Add your code below this line
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;
// Add your code above this line

console.log(averageRating);


#14

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?


#15

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)


#16

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


#17

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);

#18

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?