Reduce - Use the reduce Method to Analyze Data

Reduce - Use the reduce Method to Analyze Data
0.0 0

#1

I’m having a bit of trouble understanding the reduce method. I read through the documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) but still not getting it.

In the reduce challenge in the Beta, I solved the challenge but my code is a mess (in my opinion). I think I could have done this by simply using the reduce method.

Can someone check out my code and advise how I could have done this more efficiently, with an Explain It LIke I’m Five approach as I don’t really understand the parameters for the reduce callback function and how those can be leveraged.

Here was my solution:

// the global variable
var watchList = [
                 {  
                   "Title": "Inception",
                   "Year": "2010",
                   "Rated": "PG-13",
                   "Released": "16 Jul 2010",
                   "Runtime": "148 min",
                   "Genre": "Action, Adventure, Crime",
                   "Director": "Christopher Nolan",
                   "Writer": "Christopher Nolan",
                   "Actors": "Leonardo DiCaprio, Joseph Gordon-Levitt, Ellen Page, Tom Hardy",
                   "Plot": "A thief, who steals corporate secrets through use of dream-sharing technology, is given the inverse task of planting an idea into the mind of a CEO.",
                   "Language": "English, Japanese, French",
                   "Country": "USA, UK",
                   "Awards": "Won 4 Oscars. Another 143 wins & 198 nominations.",
                   "Poster": "http://ia.media-imdb.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1_SX300.jpg",
                   "Metascore": "74",
                   "imdbRating": "8.8",
                   "imdbVotes": "1,446,708",
                   "imdbID": "tt1375666",
                   "Type": "movie",
                   "Response": "True"
                },
                {  
                   "Title": "Interstellar",
                   "Year": "2014",
                   "Rated": "PG-13",
                   "Released": "07 Nov 2014",
                   "Runtime": "169 min",
                   "Genre": "Adventure, Drama, Sci-Fi",
                   "Director": "Christopher Nolan",
                   "Writer": "Jonathan Nolan, Christopher Nolan",
                   "Actors": "Ellen Burstyn, Matthew McConaughey, Mackenzie Foy, John Lithgow",
                   "Plot": "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.",
                   "Language": "English",
                   "Country": "USA, UK",
                   "Awards": "Won 1 Oscar. Another 39 wins & 132 nominations.",
                   "Poster": "http://ia.media-imdb.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE@._V1_SX300.jpg",
                   "Metascore": "74",
                   "imdbRating": "8.6",
                   "imdbVotes": "910,366",
                   "imdbID": "tt0816692",
                   "Type": "movie",
                   "Response": "True"
                },
                {
                   "Title": "The Dark Knight",
                   "Year": "2008",
                   "Rated": "PG-13",
                   "Released": "18 Jul 2008",
                   "Runtime": "152 min",
                   "Genre": "Action, Adventure, Crime",
                   "Director": "Christopher Nolan",
                   "Writer": "Jonathan Nolan (screenplay), Christopher Nolan (screenplay), Christopher Nolan (story), David S. Goyer (story), Bob Kane (characters)",
                   "Actors": "Christian Bale, Heath Ledger, Aaron Eckhart, Michael Caine",
                   "Plot": "When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to terms with one of the greatest psychological tests of his ability to fight injustice.",
                   "Language": "English, Mandarin",
                   "Country": "USA, UK",
                   "Awards": "Won 2 Oscars. Another 146 wins & 142 nominations.",
                   "Poster": "http://ia.media-imdb.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_SX300.jpg",
                   "Metascore": "82",
                   "imdbRating": "9.0",
                   "imdbVotes": "1,652,832",
                   "imdbID": "tt0468569",
                   "Type": "movie",
                   "Response": "True"
                },
                {  
                   "Title": "Batman Begins",
                   "Year": "2005",
                   "Rated": "PG-13",
                   "Released": "15 Jun 2005",
                   "Runtime": "140 min",
                   "Genre": "Action, Adventure",
                   "Director": "Christopher Nolan",
                   "Writer": "Bob Kane (characters), David S. Goyer (story), Christopher Nolan (screenplay), David S. Goyer (screenplay)",
                   "Actors": "Christian Bale, Michael Caine, Liam Neeson, Katie Holmes",
                   "Plot": "After training with his mentor, Batman begins his fight to free crime-ridden Gotham City from the corruption that Scarecrow and the League of Shadows have cast upon it.",
                   "Language": "English, Urdu, Mandarin",
                   "Country": "USA, UK",
                   "Awards": "Nominated for 1 Oscar. Another 15 wins & 66 nominations.",
                   "Poster": "http://ia.media-imdb.com/images/M/MV5BNTM3OTc0MzM2OV5BMl5BanBnXkFtZTYwNzUwMTI3._V1_SX300.jpg",
                   "Metascore": "70",
                   "imdbRating": "8.3",
                   "imdbVotes": "972,584",
                   "imdbID": "tt0372784",
                   "Type": "movie",
                   "Response": "True"
                },
                {
                   "Title": "Avatar",
                   "Year": "2009",
                   "Rated": "PG-13",
                   "Released": "18 Dec 2009",
                   "Runtime": "162 min",
                   "Genre": "Action, Adventure, Fantasy",
                   "Director": "James Cameron",
                   "Writer": "James Cameron",
                   "Actors": "Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang",
                   "Plot": "A paraplegic marine dispatched to the moon Pandora on a unique mission becomes torn between following his orders and protecting the world he feels is his home.",
                   "Language": "English, Spanish",
                   "Country": "USA, UK",
                   "Awards": "Won 3 Oscars. Another 80 wins & 121 nominations.",
                   "Poster": "http://ia.media-imdb.com/images/M/MV5BMTYwOTEwNjAzMl5BMl5BanBnXkFtZTcwODc5MTUwMw@@._V1_SX300.jpg",
                   "Metascore": "83",
                   "imdbRating": "7.9",
                   "imdbVotes": "876,575",
                   "imdbID": "tt0499549",
                   "Type": "movie",
                   "Response": "True"
                }
];

// Add your code below this line

let averageRating;

let arr = watchList.map(function(element) {
  return {director: element.Director, rating: element.imdbRating * 1};
});

arr = arr.filter(function(element) {return element.director === "Christopher Nolan";});

arr = arr.map(function(element) {
  return element.rating;
});

let sumRating = arr.reduce(function(acc, cur ) {
  return acc + cur;
});

averageRating = sumRating/arr.length;

// Add your code above this line

console.log(averageRating); 


#2

Is it supposed to return the average rating of Christopher Nolan’s films? I don’t know the challenge off the top of my head.


#3

Correct. Apologies, here are the instructions and link to the challenge:

The variable watchList holds an array of objects with information on several movies. Use reduce to find the average IMDB rating of the movies directed by Christopher Nolan. Recall from prior challenges how to filter data and map over it to pull what you need. You may need to create other variables, but save the final average into the variable averageRating. Note that the rating values are saved as strings in the object and need to be converted into numbers before they are used in any mathematical operations.


#4

I would probably use a filter to create an array of just Nolan’s films and then do a reduce.

Spoiler
const nolanFilms = watchList.filter((film) => film.Director === "Christopher Nolan");
const averageRating = nolanFilms.reduce((acc, curr) => acc + Number(curr.imdbRating), 0)/nolanFilms.length;

#5

My approach only uses reduce as follows. See comments as to how the variables relate to reduce parameters and callback function arguments in documentation.

let count = 0; // to hold count of Christoper Nolan movies
const ratingsSum = watchList.reduce( (sum, movie) => {  // sum is the accumulator and movie is the currentValue in the watchList array 
    if (movie.Director === "Christopher Nolan") {
      count++;  // increments count for Christopher Nolan movies
      return sum + Number(movie.imdbRating); // returns new sum with rating added
    }
    return sum; // still must return existing value of sum
}, 0); // initializes sum to 0 using optional initialValue parameter
const averageRating = ratingsSum / count;

A more condensed version of the above code would make use of the ternary operator.

let count = 0;
const ratingsSum = watchList.reduce( (sum,movie) =>  movie.Director === "Christopher Nolan"
  ? (count++, sum + Number(movie.imdbRating)) : sum, 0);
const averageRating = ratingsSum / count;

#6

Thanks both!

@randelldawson so for the reduce method call, first argument is the aggregated total, 2nd is the element of the array. is that accurate?


#7

Yes, that is correct in this example. Though, the accumulator does not have to be a value like a total or sum. It could be an array, object, string, etc…


#8

Ok perfect! I think I was making things a little more complicated when the gears were spinning. Good to know it doesn’t have to be a value, all of the documentation examples that made sense were just summing up the entire array, so I was having a little trouble understanding what was going on for other use cases.

Another small question, I find that for filter, map, reduce, it helps me to keep the parameter name “element” to keep things straight in my head. Is that a poor practice, should I be more specific to the use case in terms of readability. I’ve seen element used as the standard for map, but then accumulator, currentValue used for filter. Is there a best practice there, assuming more precise the better.


#9

It is all about readability. Variable names in general should describe the data contained in them. It helps when someone else reads your code or when you revisit code you have not viewed in a while. Otherwise, you end up having to clutter up your code with comments explaining what things are, which is not a best practice.

The comments I put in my example code above were strictly meant for educational purposes. I would not normally write comments like that. Comments should explain why you did something vs. what you did. What you did should be evident because the code is readable. Hopefully, that makes sense to you.

An example of more condensed ( I assume that is what you meant by “precise” above), would be to shorten my 2nd condensed solution by shortening the variables names.

let c = 0;
const rs = watchList.reduce( (s,m) =>  m.Director === "Christopher Nolan"
  ? (c++, s + Number(m.imdbRating)) : s, 0);
const averageRating = rs / c;

Which one do you think is more readable? Which ones best explains what is happening in the code?


#10

Makes sense, by precise I meant relative to what is being described, like you had in your original code. I’ll probably start by writing out what helps me understand the logic, and then switch it to more descriptive names afterwards, until it becomes more natural for me.

Thanks for all the info, has been very helpful!


#11

I wrote this ages ago for someone in the chatrooms, might be helpful: https://gist.github.com/DanCouper/599c771e7214e81bd1a8d8bc33746378

Also, you don’t need to map here, reduce does anything any of the other array functions can do, (edit: this post was sitting around a while before I hit submit, so a few other people suggested the same thing) eg:

var nolanRatings = watchlist.reduce(function(watchItem, ratings) {
  var rating = Number(watchItem.imbdRating);
  if (watchItem.director === 'Christopher Nolan') {
    return {total: ratings.total + rating, count: ratings.count++}
  } else {
    return ratings;
  }
}, {total: 0, count: 0});

var nolanAverage = nolanRatings.total / nolanRatings.count;

#12

This approach is interesting but if you utilize higher order functions (map and reduce) it might look cleaner

var movies = watchList.filter(n=>n.Director == "Christopher Nolan").map(m=>Number.(m.imdbRating))
var averageRating = movies.reduce((a,b)=>a+b)/movies.length

#13

One reduce suffices in my opinion. Using a filter, map and reduce almost triples the number of iterations performed.


#14

Thank you for this example. My solution was just like Ariel’s but I got lost with parasynthesis and commas… When I saw your solution, I was like “What’s going on here? Why count? What sum and movie do?” I had to run the code and then to write it down to comprehend completely. And I totally loved it! (However, the logic was very unclear at first)


#15

This is my solution:

let filtered = watchList.filter(el => el["Director"] == "Christopher Nolan");
let averageRating = filtered.map(el => parseFloat(el["imdbRating"])).reduce( (x,y) => x + y) / filtered.length;

console.log(averageRating);

#16

So many good answers above. As I like seeing all the different responses, I’ll throw my way of doing it out here too:

var nolanDirected = [];
watchList.map(function(ele, i, arr) {
  if (ele.Director == "Christopher Nolan") {
    nolanDirected.push(ele.imdbRating);
  }
});

var averageRating = nolanDirected.reduce((acc, cV) => Number(acc) + Number(cV)) / nolanDirected.length;

Thanks for sharing, everyone.


#18

I love that everybody has slightly different solutions :grinning:
somebody mentioned that you don’t need .map at all, since you’re using .reduce
I did it step by step, first all movies directed by Nolan, then all ratings which are then converted into numbers and then reduced at the end.
Anyhow, i’ll just throw in my solution, this could definitely be reduced

var averageRating;
var rating = [];

watchList.map(function(x) {
      if (x["Director"] === "Christopher Nolan") {
            rating.push(parseFloat(x["imdbRating"]))
      }
   averageRating = rating.reduce(function(a, b) {
      return a + b;
  }) / rating.length;
});


console.log(averageRating); 

Thanks for sharing solutions :grinning:


#19

what does the ‘, 0)’ at the end of reduce do…???


#20

It’s the initial value. Array.reduce()


#21

Is it something like a fallback value…???