Functonal Programming: Use the reduce Method to Analyze Data

Functonal Programming: Use the reduce Method to Analyze Data
0
// 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/[email protected]@._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/[email protected]_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/[email protected]@._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/[email protected]@._V1_SX300.jpg",
                   "Metascore": "83",
                   "imdbRating": "7.9",
                   "imdbVotes": "876,575",
                   "imdbID": "tt0499549",
                   "Type": "movie",
                   "Response": "True"
                }
];

// Add your code below this line
let imdb=[];
let arr=watchList.filter((item)=>(item.Director=="Christopher Nolan"));

arr.map((item)=>imdb.push(item.imdbRating));
//arr.filter((item)=>imdb.push(item.imdbRating));

var averageRating=imdb.reduce((total, item)=> total + item);
// Add your code above this line
console.log(arr);

//console.log(arr+"\n"+imdb+"\n"+averageRating);
console.log("\n"+imdb+"\n"+averageRating);

Some questions:

  1. When I run this program in node.js, the array arr is displayed with all the entries for director Christopher Nolan. But the program when run on this fcc site, it just outputs [object Object],[object Object],[object Object],[object Object]. So why is it behaving differently?
  2. The line that I have that uses map and the following commented line that uses filter both work to return an array with only the imdbRating values. So I dont see when to use map and when to use filter.
  3. Why is the reduce function not working? it seems to be written the correct way.

For this lesson you only need to use the reduce method. The problem with your reduce method is that first you should be iterating over the watchList array, but you instead are iterating over an empty imdb array. Also, you are not understanding what total and item represent inside the reduce callback function. item will be an object. You will need to write logic that only checks if the Director property value of each object is “Christopher Nolan”. If it is, then you need to capture the sum of all the ratings of his movies and divide by the number of those those movies in the watchList array.

EDIT: Just to be clear, you can us other method like filter and map in conjunction with the reduce method, but you can technically solve this challenge without them.

When I use the filter statement I have, I get back an array with all the Nolan directed films. When I use the map statement (or the filter statement that is commented), I get the imdb array that has the different imdbRating values (4 of them). So I get this imdb array with the 4 values whether I use map or filter, I wonder how to know when to use which one. And finally, even though the imdb array has the 4 values, the reduce function gives me the imdb array without the commas, instead of the sum. As I understand, total is the accumulator and item refers to every single numeric data.

As I read it, the problem does say use map and filter to pull the data that we want, namely the imdbRatings values, and then use reduce to give the sum… So what am I doing wrong in the reduce function?

map and filter return new arrays. map is used when you need to create a new array containing a new value for each element based what your return during each iteration. You don’t need to use the push method to create a new array. You currently have arr which contains all the movie objects where Christopher Nolan was the Director. You can now call the reduce method on arr to calculate the total of each of these object’s imdbRating. The second argument (item) be the movie object, so you will need to refer to item.imdbRating in some way to get the sum. Just keep in mind that the ratings are strings, so you need to convert them to numbers when performing the sum. Lastly, since you need the average rating, you will needed to divide by the total number of movies directed by Christopher Nolan. Since arr will already contain these movies, think about an array property you could reference to get the total number of movie objects in arr.

Just to recap, you do not need any of the following lines:

let imdb=[];
arr.map((item)=>imdb.push(item.imdbRating));
//arr.filter((item)=>imdb.push(item.imdbRating));

Also, you will need to use reduce with arr like:

var averageRating = arr.reduce((total, item) => /* some code goes here */ ) / /* some code goes here */;

const arr=watchList.map((item)=>{if (item.Director==“Christopher Nolan”) return item.imdbRating;});

gives me 5 items, not 4, the last one is undefined because the director is not christopher nolan, why am I getting 5, the last iteration evaluates to false right?

.map can never change the number of items in the output array. The output array’s size will always be the same as the input. You can however .filter this output to remove the undefineds.

You don’t need map to solve this challenge. You can use filter and reduce or just reduce.

I have the array of imdbRatings in arr = [‘8.8’,‘8.6’,‘9.0’,‘8.3’] and the length of arr is 4
Now to add numbers with the reduce function, where the numbers are in string format, shouldnt the statement be
let tot=arr.reduce((total,n)=>total+parseFloat(n));
I cant get it to work, cause tot prints out as 8.88.69.08.3

If however, I parse the string values when I map it, by saying

const arr2=arr.map((item)=>(parseFloat(item.imdbRating)));
var averageRating=(arr2.reduce((total, item)=> total + item));

I get the array of the ratings as [8.8,8.6,9.0,8.3]
and the correct answer of 8.675.

So, why does the parseFloat work when I map the values from string to floating point, but does not work in the reduce function when I try to convert them?

If you do not specify an initial value for the accumulator, then in the first iteration, total will be the first element in the array and item will be the second element in the array. This means total would still be a string. When you concat a string with a number, it is still a string.

In your code above, tot would be ‘8.88.698.3’

@Randell,

Yes if the initial value of the accumulator is a string , then any values concatenated to it would first be converted to strings and then appended to the end. But what determines whether the initial value of the accumulator is a string or a number. Im assuming it is the type of the element, but will it be the type of element that is in the original array (type is string) or the type passed into the reduce function (float because the element type passed in is parsed with parseFloat(string).

You can specify what the initial value of the accumulator starts at. I would recommend the value 0, then your code would work.

check the syntax of reduce() in the documentation (linked below), but as explained above if you don’t specify a starting value than the starting value of the accumulator it will be the first value of the array, as your array is of strings, than that is what the type would be

@ieahleen,

Yes, one way to specify the type of the accumulator would be to explicitly specify it as an argument, but I was assuming that the type could also be determined according to the second argument that was passed into the reduce function, so it would be a string if the second argument was passed in as a string and a number if the second argument was passed n as a number. I thought by using parseFloat on the string argument before passing it in to the reduce function, we were passing in a number and therefore the type of the accumulator would be number. Guess that’s not the way it works!!

if you don’t specify a second argument to the reduce method, then the callback function will have the element at index 0 as total, and the element at index 1 as item for the first iteration - note that the item at index 0 is taken as it is, no modifications, no change of type or anything, just like that

if you specify a starting value for the accumulator (that you have called total) then total will start with that value and item will be the element at index 0 for the first iteration

so it is useful for you to specify a second argument in the reduce method, it means less work to do in these things

If the first item in the array is a string. But if
@ieahleen: Yes, the accumulator initial value should be a string, if the first item gets passed in as a string. But if the first element is passed in as a number then the initial value of the accumulator should be that number. And I thought that by specifying that the first element had to be parsed into a float, we would be passing a number so that the initial value of the accumulator is a number. But I guess it doesn’t work that way, because when I store the array as a collection of numbers (as shown earlier in this post), everything works without a hitch. So, the accumulator type depends on the value of the element in the array, and not the way it is passed into the reduce function.

it depends on the value type of array elements!

if you set a starting value for your accumulator in the reduce() method and use parseFloat() on the item parameter in the callback of reduce() then you don’t need to add the map method and it means less resources needed to run yout code

Try passing an object such as this:

{ TotalRating: 0, MovieTotal: 0 }

in as the starting accumulator; then accumulating and returning it in each iteration through the loop as the new accumulator.

Then add console logging to each iteration to follow the progress of your accumulator.

Finally accept the result and calculate the desired average from the returned TotalRating and MovieCount properties.