loading...

Useful JS functions you aren't using: Array.filter

aeiche profile image Aaron Eiche ・2 min read

Suppose you had a list of students in a classroom, and you want to know how many are girls. You might write something like this:

var students = [
  {name:"Davey", gender:"male",age:"10"},
  {name:"Susie", gender:"female",age:"10"}, 
  {name:"Frank", gender:"male", age:"10"},
  {name:"Lindsay", gender:"male", age:"11"},
  {name:"Terry", gender:"female", age:"10"},
  {name:"Melissa", gender:"female", age:"11"}
]

var girlsCount = 0;
for(s = 0; s < students.length; s++){
    if(students[s].gender === "female"){
        girlsCount++;
    }
}

console.log("The Number of girls is: " + girlsCount);
//The Number of girls is: 3

That's a fine, functional way to do it. If you utilize the language features of Javascript, you can save yourself some time. Try Javascript's filter function attached to every array!

var girls = students.filter(function(s){return s.gender === "female" });
console.log("The Number of girls is: " + girls.length);

Filter returns a new array that is a subset of the array you call it on, wherein the callback function returns either true or false. A true value will include that item in the new array. A false value will leave it out. This makes it easy to write filters that can accommodate however simple or complex you need. In our class, lets say we instead want to find all the students that are eleven, and have the letter 'e' in their name. With a filter, we just need to return true if a student object has those two things:

var eAndEleven = students.filter(function(s){return s.name.match(/e/gi) && s.age == 11});
eAndEleven.map(function(s){console.log(s.name)});

We can use the Array.map function to output all those matching students to our console.

Speaking of Array.map, suppose you need to apply some transformations to your array, before you generate a subset of it. Because these methods return new arrays, you can use them chained together:


  students
  .map(function(s){ s.age++; return s})
  .filter(function(s){ return s.age < 12})
  .map(function(s){console.log(s.name)})

This code adds a year to everyone's age, then filters out those who are less than 12, and the final map outputs the filtered array to the console.

Like map, filter allows you to make things more compact and utilizes built-in language features to make your life easier as a developer.

If you're using ES6/ES2015, you can utilize arrow functions to make your code even more compact:

  students
  .map(s=>{ s.age++; return s})
  .filter(s=>{ return s.age < 12})
  .map(s=>{console.log(s.name)})

I got lots of comments on twitter about my first article, Useful JS Functions You Aren't Using: Array.map, which encouraged this as a series, and things to include. Thank you to folks for contributing back, and offering suggestions.

Discussion

pic
Editor guide
Collapse
buntine profile image
Andrew Buntine

Nice work. Just a little note on the last couple of examples. If you want to do this in a functional style, it's important not to mutate state in the map function.

In your last example, the first map actually mutates the original collection with the ++ operator. In order to keep the original collection unchanged you can use the object spread operator in upcoming versions of Javascript, but for now you'll need to do something like this:

students.map((s) => Object.assign({}, s, {age: s.age + 1}))

The second call to map also has the same issue in that it has a side-effect (I/O in this case) that causes the entire expression to return an array of undefined. Obviously there is no way to do I/O without a side-effect and thus the higher-order function you should use here is forEach (which perhaps is the topic of an upcoming blog post?)!

Finally:

students
  .map(s => Object.assign({}, s, {age: s.age + 1}))
  .filter(s => s.age < 12)
  .forEach(s => console.log(s))
Collapse
xherno profile image
herno

Quick Fix to get this example working:

students
.map(s => Object.assign({}, s, {age: parseInt(s.age, 10) + 1}))
.filter(s => s.age < 12)
.forEach(s => console.log(s));

Age should be parsed as integer, in order to get the addition working, otherwise it will be treated as a string and will result i.e, for age 10, in age: 101

Collapse
buntine profile image
Andrew Buntine

Sure, although I'd argue that a better approach would be to represent the age as an integer in the data structure. Especially if the program is planning to perform arithmatic on it! :)

Collapse
aeiche profile image
Aaron Eiche Author

This is a really excellent point Andrew, and something I totally missed while I was writing it. Thank you for the information and examples!

Collapse
vidup profile image
Victor Dupuy

For the record, it's also possible by spreading the object, although it won't work everywhere at the moment, depending on your ES version:

students
  .map(s => ({...s, age: s.age + 1}))
  .filter(s => s.age < 12)
  .forEach(s => console.log(s))
Thread Thread
tjinauyeung profile image
Tjin

Clean it even more by extracting callbacks

const addYearToAge = person => ({...person, age: person.age + 1})
const isUnderTwelve = person => person.age < 12

students
  .map(addYearToAge)
  .filter(isUnderTwelve)
  .forEach(console.log)
Thread Thread
vidup profile image
Victor Dupuy

It's beautiful :O