Functional Programming: Use the reduce Method ,to Analyze Data

Hi campers, this challenge has taken from me a long time to solve, here it is:
https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/functional-programming/use-the-reduce-method-to-analyze-data/

finally this is my solution :

// Add your code below this line
var average=watchList.map(function(item){
    return {Director:item.Director,rating:item.imdbRating};
}).filter(function(item){

     if(item.Director==="Christopher Nolan"){
        
       return {Director:item.Director,rating:item.imdbRating};
    }
}).map(function(value){
    let num=Number(value.rating);
    return num;
}).reduce(function(sum,currentValue,index,arr){
    sum+=currentValue;
    if(index===arr.length-1){
        return sum/arr.length; 
    }else {
        return sum
    }
   
   
});


var averageRating=average;

// Add your code above this line

console.log(averageRating); 

this worked well

but I don’t know why in reduce method I couldn’t use just if statment without else statment :

var average=watchList.map(function(item){
    return {Director:item.Director,rating:item.imdbRating};
}).filter(function(item){

     if(item.Director==="Christopher Nolan"){
        
       return {Director:item.Director,rating:item.imdbRating};
    }
}).map(function(value){
    let num=Number(value.rating);
    return num;
}).reduce(function(sum,currentValue,index,arr){
    sum+=currentValue;
    if(index===arr.length-1){
        return sum/arr.length;  //here it retuns NAN 
    }
   
   
});

why it retuns NAN ,and my second question did I use a long approach for this challenge .
thank you guys in advance.

Reduce must always return a value.

1 Like

Reduce runs the function on the previous value and the current value. Then the return value of that function becomes the previous value, and the function runs on this new previous value and new current value. Then the return value of…and so on until you get to the end of your array. If you don’t return anything at any point, the return value will be undefined, which will cause the operation to break down, because there will now be no previous value

Also just re the code: I would avoid doing two things in the reduce (ie doing the calculation if you’re on the final item), just make your functions do one specific thing, there’s no real need to add that complexity - I mean it’s fine, it just makes things slightly confusing. More importantly, that’s not how filter works:

if(item.Director==="Christopher Nolan"){  
  return {Director:item.Director,rating:item.imdbRating};
}

should just be

return item.Director==="Christopher Nolan";

At the minute, your code is only working by accident. The function you give to filter has to return true or false, it doesn’t return any value other than that. If the function returns true, that item gets kept, if it returns false that item gets dropped. So your code is working because:

  • if the director is Christopher Nolan:
// this is `true`:
if(item.Director==="Christopher Nolan"){
  // so this code runs, and the object coerces to `true`: you are not
  // returning an object here, you are just returning the value `true`
  return {Director:item.Director,rating:item.imdbRating};
}
  • if the director is not Christopher Nolan:
// this is `false`:
if(item.Director==="Christopher Nolan"){
  // this code does not run
  return {Director:item.Director,rating:item.imdbRating};
}
// the function returns `undefined`, which coerces to `false`,
// so the function returns `false`

What filter leaves you with is the array you started with, but with any entries where Nolan is not the director dropped from it: you are not returning {Director: item.Director, rating:item.imdbRating};

There is also no need to build new objects, it does not make it any easier to access the values - the one you want, imdbRating can be accessed like object.imdbRating regardless of if there are 2 entries or 2 million entries in the object.

var nolanFilms = watchList.filter(function(item){
  return item.Director === "Christopher Nolan");
});
var ratings = nolanFilms.map(function(value){
    return Number(value.imdbRating);
}).reduce(function(sum,currentValue){
    return sum + currentValue;
}, 0);

var averageRating = ratings / nolanFilms.length;

or even:

var ratings = watchList.reduce(function(accumulator, film) {
  if (film.Director === 'Christopher Nolan') {
    // it's a Nolan film: add the rating to the total and increment the number of films
    return {
      total: accumulator.total + Number(film.imdbRating),
      films: accumulator.films + 1;
    }
  } else {
    // not a Nolan film: don't add to the totals
    return accumulator;
  }
}, {total: 0, films: 0});

return ratings.total / ratings.films;
2 Likes

thank you for your time , you have explained to me this very well .

1 Like

const numOf = watchList.filter(el => el.Director === “Christopher Nolan”).length;
const averageRating = watchList.filter(el => el.Director === “Christopher Nolan”).map(el => Number(el.imdbRating)).reduce((pre, cur) => pre + cur)/ numOf;
console.log(averageRating);