Im sure this is a weird solution to Functional Programming: Use the reduce Method to Analyze Data

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

Here is my solution. It was not working at first because even though the map returned only 4 numbers it had an empty item at the end of the array so instead of newArr.length being 4 it was 5 so I had to pop()the last item off.

Initially I wrote it as var averageRating = watchList.map(rating).reduce((a, b)) a + b) / averageRating.length;
Im sure that last item on the end of the array was causing NaN.

Is there a reason it added that 5th item?

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

 
var newArr = watchList.map(rating);
newArr.pop();
var averageRating = newArr.reduce((a, b) => a + b) / newArr.length;
function rating(item) {
  if (item.Director == "Christopher Nolan") {
    return parseFloat(item.imdbRating);
    
  }
}

// Add your code above this line

console.log(averageRating); 
1 Like

Map does not filter array elements. It tranforms ALL the elements of the array into something else. IF you do not return a value inside a map callback for some particular element the value of that element in a newArr will be undefined. These things are easily caught by console.log-ing some of data structures in your code as you go, step by step. Learn to debug, it will come in handy :slight_smile:

Oh i see, yea I did log and noticed an empty element in the array. So if I would have used filter instead of map it would have worked?

Filter would help you remove some of the elements of the array that you do not need going forward, yes. But you’ll obviously still need to use map after that, to extract the particular prop you need.

1 Like

Sweet thanks for the info, I just tried it. I filtered just the director ones then mapped the rating. Appreciate the help.

this is the new code I used. I was able to use the same function for filter and map.

// Add your code below this line

 var newArr = watchList.filter(rating);
 var averageRating = newArr.map(rating).reduce((a, b) => a + b) / newArr.length;

function rating(item) {
  if (item.Director == "Christopher Nolan") {
    return parseFloat(item.imdbRating);
  }
}
// Add your code above this line

console.log(averageRating); 
1 Like

For extra credit, can you come up with a way to do this in a single step? Array.reduce() is a WILDLY powerful tool - it can be used to do the same things as both Array.filter() and Array.map(). It’s not often discussed at the introductory level (as this lesson kind of is), but it’s true. If you set up a reducer function, you can use it to both filter the array, and to compact that array to return just the data you want.

If you’re interested, send me a DM - I’m not sure it is wise to post the solution to that here, even as a spoiler-wrapped thing. It’s kind of “giving away the farm”.

ALSO: please do wrap your solution code in the spoiler tags, so that others aren’t given the answer if they search this post later. :wink:

Separation of concerns, single responsibility principle, readability ? :stuck_out_tongue: Remember, your code has to be easily readable by humans. And to me using an object as an accumulator in reduce is hardly a subject worth broaching to newbie devs who tread on shaky ground.

It’s cute, but again - hardly a good idea. What happens if one of the elements you are testing this way is a falsy value, like a 0, and the director is Christopher Nolan? Doesn’t make much sense for this particular situation because Nolan rocks, but can happen if you think using filter with a callback that returns numbers instead of booleans is ok.

Wouldn’t the function just factor in the 0 as part of the rating?

filter remove all the elements for which the callback return false or a falsy value, 0 is a falsy value, it wouldn’t be included in the filtered array.

Oh i see, thanks for the clarification.

I hardly think that someone who has gotten to this point in the curriculum, working on functional programming, could truly be considered a “newbie dev”. Having gotten this far, I would guess that many, if not most, have realized that:

  1. FCC is great, but hardly comprehensive. Research elsewhere is a must, and there will be things that challenge us to learn more than FCC might offer.
  2. There is always more than one way to do a thing.

Now, you may feel your reasons are valid, and complete and convincing. And for you, that may be so. But there are just as many valid reasons to consider my approach.

  1. Let’s play a hypothetical. Suppose the IMDB array contained 100,000 records, of which say 3,000 were within the search criteria. Using the filter approach, we take an array of 100,000 members and return one with 3,000. Then we have to map those 3,000 array members into… another array of 3,000 members. Then, finally, we reduce that to a single number. Using a reduce, written properly, we take 100,000 array members and return all the relevant data… in a single pass.
  2. The approach I outline is used, commonly, in the field. It isn’t a cute hack, but a recognized and useful tool, when implemented properly. If one chooses to remain unaware of the capability of the tool, one is asking to be left out of the conversation. “Newbie coders” will encounter something like I outline, and being unaware of how it works won’t make it less of a problem - but being aware of, and comfortable with, this approach will.
  3. Returning an object from a reduce call is, in fact, pretty common. What do you suppose React’s setState is doing? What do you suppose is the core of Redux? Reducers, taken beyond the simplest case.

I don’t say you’re wrong, but for me, I’m delighted to have learned a powerful use of reduce, and I don’t plan to stop suggesting others challenge themselves to do the same.

I think they were referring to my solution as cute, not yours. I do thank you for that knowledge as I’m sure it will prove useful in the future.

TL;DR: I understand where you are coming from, but we’ll agree to disagree. I admire your passion and I thank you for a long and thoughtful reply. I hope i did not offend you, take care.

Plenty of people just focus on FCC and don’t do anything else. And challenges on FCC, for the most part, have a nasty habit of teaching people to copy-paste instead of thinking, which is a whole different issue. The only section that someone had to complete to get to “functional programming” and this challenge that can be called independent programming is “basic algo scripting” and plenty of challenges there are just one liners that test your knowledge of very basic vanilla JS array/string methods, so it’s not that mind boggling to think that yes, most people who get to this point are still very new and don’t know much.

There is always more to learn. There is always more than one way to do a thing. You are right. That is why you have to pick and choose your battles. Here’s the problem with learning “concepts” in a bubble - you invent a problem to learn the syntax/technique. That’s IF you are savvy enough to try and force yourself to “play around” and find some problem/exercise that can take advantage of said concept/method/technique. Instead of having a problem/project/task/requirement and having to come up with the best way to solve the problem with the tools the language has to offer, which is a much better way to learn something. That’s why i’m hopeful for the new FCC curriculum that was announced recently. More focus on solving real problems in building real apps and learning the curriculum as means to an end, not because these things “exist” in javascript and you are “supposed” to know them.

Now, as for your other points:

  1. This is way out of the newbie programmer’s league, i am sorry. And for things like this, most new people will ask more senior devs for advice on how to best implement the solution/which techniques to use. And rightfully so. You have some experience working with filter/map/reduce on a regular basis, feel comfortable with them and are ready to take your skills to the next level? The task you gave him is a great task. This is your first day of learning map/filter/reduce, you had no idea what these were yday?

Sorry, in that situation this kind of exercise can only lead to frustration. And even if you look up the code for this that others have wrote and manage to analyze it, without understanding where and WHY this can be useful it’ll go in and out of your head and you’ll forget it in a few days. That’s just the nature of learning. We remember how we solved ‘the real problems’ better than ‘mock problems’. That is why “learning by the way of writing apps” is the most effective kind of learning.

  1. I used “cute” to describe the solution by komegga, not your suggestions. I already addressed the learning and retention of concepts in my reply to 1).

  2. It is pretty common. Sure. But it’s an advanced concept. Are you really going to be comparing the implementation of Redux, a library used by millions of professionals and solutions implemented by someone who is just trying to understand reduce after seeing it for the first time? I know plenty of professional developers who have written JS for years who don’t know how reduce works all that well / who barely use it.

As aspiring programmers who are trying to learn we only have so much time in our lives. We have to spend it wisely, and trying to learn stuff like this THIS early won’t lead to much progress, especially when there are a ton of things that you can learn that will give you a much bigger boost.

After learning more i went back to this and tried to make it better and this is what I have come up with.

let newArr = watchList.filter(item => item.Director == "Christopher Nolan" );
 var averageRating = newArr.map(item => parseFloat(item.imdbRating)).reduce((a, b) => a + b) / newArr.length;

console.log(averageRating); 

You don’t need the extra map if you are going to use reduce at the end. Technically, you don’t even need the filter if you are going to use reduce, but the code would get a bit unreadable if you did that. Use the parseFloat inside the reduce.

1 Like

Hey @RandellDawson , I felt challenged by your comment and wanted to see if I could write the solution without .map. Here’s what I’ve come up with. Is there any way to simplify this further?

function getRating(watchList){
  // Add your code below this line
    let chrisNolan = watchList.filter(movie => movie.Director === "Christopher Nolan");
    let averageRating = chrisNolan.reduce((avg, movie) => {
        return avg + movie.imdbRating / chrisNolan.length;
    }, 0)

  // Add your code above this line
  return averageRating;

As far as the .filter function, I’m not sure how to avoid using it. I tried using an if statement and a counter variable but ran into complications. Any pointers would be appreciated.

The challenge use to contain a function which returned the average rating. You can see how to solve using only reduce here. It is a bit ugly though.

1 Like

Wow! The number of parameters is a bit mind-boggling.

What caught my eye however, was how the initialValue parameter was set with values for sum and count. That was functionality I was looking for when trying to avoid using .filter.

As far as the large list of parameters, here’s what I’ve concluded:

  1. I believe {sum, count} are simply the accumulator and currentValue parameters.
  2. Since the array is what is being filtered, {Director: dir, imdbRating: rating} are not actually one of the 5 parameters but instead are simply renaming keys, am I right? Just to make it easier to read.
  3. And finally, idx and arr are the optional index and array parameters outlined in the MDN docs for the .filter function.

Any input/corrections are welcome.

To tackle your questions in order:

  1. {sum, count} is the accumulator. We’re using an object for the accumulator, and that object has two properties. But here, Randell is using object deconstruction to turn them from object properties into two variables within our callback function. Those two variables? sum and count.
  2. At each pass of the iteration, we have an array member, which happens to be an object. Again, Randell is using object deconstruction here, but a little differently. When I read a line like this in a parameter (or object deconstruction statement anywhere), in my head I hear it as an SQL statement: “SELECT Director AS dir, imdbRating AS rating FROM the current object”. Those are the only two object properties we need, but again - we aren’t dealing with the entire object inside our function, simply the two variables we’re pulling from the object, which we’ve renamed to dir and rating.
  3. And yes, idx and arr are those optional parameters. idx refers to the index of the current array member, and arr is a reference back to the original array.

So, while there are a lot of things going on, it’s still simply Array.reduce(accumulator, function(arrayMember, index, originalArrayReference){...}, accumulatorStartingValue);.

For the record? The return statement on that is BRILLIANT. I’d always simply returned the object and done the math later. The idea of conditionally returning the object OR the average, depending on whether we’re on the last array member? Absolutely blew my mind. Totally makes sense, and in retrospect I’m an idiot for missing it, but…

Randell, I bow to your brilliance. Thank you for that. LOVE learning new and more elegant solutions.

1 Like