Functional Programming: Use the reduce Method to Analyze Data

Hey guys!

I have a question about the FCC problem where you use the reduce method. Link below.
https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/functional-programming/use-the-reduce-method-to-analyze-data/
It has an object containing multiple movies, and the challenge is to find the average score of Christopher Nolan movies.

The code I have is :

var averageRating = watchList
  .filter(movie => {return movie.Director=="Christopher Nolan"})
  .reduce((sum, nolanMovies) => {
    return (sum + parseFloat(nolanMovies.imdbRating));
    },0)
  /(watchList.filter(movie => {return movie.Director=="Christopher Nolan"}).length);  // <--there has to be a better //way.
;

while this works, I feel it is kind of ugly. As you can see, I could filter out the movie list and get the sum of the ratings but didn’t know how to get the amount of movies there are without invoking the filter method again. I feel like there has to be a better way to do this but I just couldn’t find out. What would be the best way so I can convert the sum of scores into an average?

3 Likes

You could increment your count while calculating sum if your accumulator could hold both values. Like an array [0,0] or an object {sum:0, count:0}. That would give you both numbers in one pass, maybe even calculate average at the end somehow.

EDIT
I figured this out. Maybe it will help you out.

const arr = [10,20,30];

const average = arr.reduce(function(acc,el,ind,ar){
  acc = [acc[0]+el, acc[1]+1];
  if(ind >= ar.length-1){  // calculate average on last element
    acc = acc[0] / acc[1];
  }
  return acc;
},[0,0]);

console.log(average);  //20
1 Like

You can first store the filtered array in a separate variable. That way you only have to do one .filter() call.

var moviesToRate = watchList.filter(...);
var averageRating = moviesToRate.reduce(...) / moviesToRate.length;

Also note that when using arrow functions, if you’re immediately returning a new value, you can omit the braces and the return keyword altogether.

.filter(movie => movie.Director == 'Christopher Nolan')
4 Likes

I like this, definitely more elegant than the way i have it. Thank you!

just completed this challenge, and here is a solution without in-processing variable

var averageRating;

averageRating = watchList
  .filter( el => el.Director == "Christopher Nolan" )
  .map( el => +el.imdbRating)
  .reduce( (acc,cur,index) =>  (acc*index + cur) / (index + 1), 0 )
3 Likes

Could someone help explain in more detail what’s going on with the code below?

  .map( el => +el.imdbRating)
  .reduce( (acc,cur,index) =>  (acc*index + cur) / (index + 1), 0 )

In the map method, I’m curious as to what the + does. For the reduce, I’m just lost. What is acc and cur?

Appreciate it! Thank you for the help.

Hi, can someone explain the meaning of (acc*index + cur)?
I created a separate array for each step: filter, map then reduce which provides the same answer. The ‘reduce’ method looks like this sum = Rating.reduce((total,item) => (total + parseFloat(item)/Rating.length),0)
So just want to understand how other solution works.
Thanks.

  • converts strings to float value

Mine is a bit more of simple, it doesn’t use map or filter:

var movieCount = 0;
var averageRating = watchList.reduce(function(accumulator,currentValue){

if (currentValue.Director === “Christopher Nolan”){
accumulator+= Number(currentValue.imdbRating);
movieCount++;
}

return accumulator;

},0)/ movieCount;

2 Likes

Thanks for the hint, I got it work like this:

var averageRating;

var moviesToRate = watchList.filter((movie) => movie.Director === 'Christopher Nolan');

averageRating = moviesToRate.map((movie) => Number(movie.imdbRating)).reduce((val1, val2) => (val1 + val2))/moviesToRate.length;