Understanding the filter() Method in JavaScript
Breaking down common interview questions one console.log at a time.
As I prepare for technical interviews — and work through coding challenges — I find it helpful to break my problem solving process down into micro-steps. This helps me understand what’s going on beneath the surface.
Today’s question forced me to dig deeper into the filter() method and spread operator. (woot woot!)
Today’s Challenge:
// Take 2 strings s1 and s2 including only letters from a to z. Return a new sorted string, the longest possible, containing distinct letters, each taken only once - coming from s1 or s2. // Example:
// a = "xyaabbbccccdefww"
// b = "xxxxyyyyabklmopq"longest(a, b)
-> "abcdefklmopqwxy"// a = "abcdefghijklmnopqrstuvwxyz"
longest(a, a)
-> "abcdefghijklmnopqrstuvwxyz"source: https://www.codewars.com/kata/two-to-one/train/javascript
Thinking through the solution in pseudocode.
I started by sketching out a high level overview of the steps I thought I needed to take:
// 1. Combine strings s1 and s2.
// 2. Split the new string into an array of characters.
// 3. Filter for unique values into a new array.
// 4. Sort the new array from a to z and join it together, removing the commas.
Step 1:
In this example we are given the below two strings:
var a = “xyaabbbccccdefww”;
var b = “xxxxyyyyabklmopq”;
A simple way to combine two strings is to use the addition operator: +=
Let’s set a third variable equal to an empty string:
var c = '';
Using +=
allows us to add something to c
without reassigning its value.
For example, if we add a
to c
using the assignment operator:
c += a
-> "xyaabbbccccdefww"// When we call:
c// Its value is:
-> "xyaabbbccccdefww"
Now if we add b
to c
using the assignment operator:
c += b
// We get:
-> "xyaabbbccccdefwwxxxxyyyyabklmopq"// Call the value of c again, it equals:
-> "xyaabbbccccdefwwxxxxyyyyabklmopq"
To make it more concise we can set c
equal to a += b
and get the same result:
var c = a += b;-> "xyaabbbccccdefwwxxxxyyyyabklmopq"
Step 2:
Before we can get to step 3 and filter for the unique values in the string, we need to split it into an array.
We can do this by using the spread operator in ES6. Automagically this transforms a string into an array!
// Using the spread operator:
[...c]-> ["x", "y", "a", "a", "b", "b", "b", "c", "c", "c", "c", "d", "e", "f", "w", "w", "x", "x", "x", "x", "y", "y", "y", "y", "a", "b", "k", "l", "m", "o", "p", "q"]
This is the same as using the split() method:
c.split('');
Let’s set another variable equal to this new array by doing:
var d = [...c];
Step 3:
Now we can apply the filter method to our d
array.
To help make sense of what filter() is doing, let’s say we wanted to filter all the x’s into a new array. How would we normally do that?
We could loop through the array using a for loop, check if each value equals “x”, and if so, push it to a new array. Then return the new array.
// Given:
var a = "xyaabbbccccdefww";
var b = "xxxxyyyyabklmopq";
var c = a += b;
var d = [...c];// We could do:
function filterX(arr) {
newArr = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] === "x") {
newArr.push(arr[i]);
}
}
return newArr;
}// Call the function passing d into it:
filterX(d);-> ["x", "x", "x", "x", "x"]
Using filter makes our solution more succinct:
var e = d.filter(function(value) {
if (value === "x") {
return value;
}
})
More succinctly still, in ES6 we could write:
var e = d.filter(value => value === "x" )
In looking at MDN’s docs for the filter() method, in addition to passing the value of each element, we can pass the index of the element and the array itself.
To filter out unique values we could use the following function. But what is this actually doing? Let’s break it down.
let e = d.filter(function(v, i, self) {
return self.indexOf(v) === i;
});
console.log(e);// Returns:
-> ["x", "y", "a", "b", "c", "d", "e", "f", "w", "k", "l", "m", "o", "p", "q"]
How is self.indexOf(v) === i
filtering out unique values?
Let’s nest the above into our ‘longest’ function and log self.indexOf(v)
to the console:
function longest(s1, s2) {
var c = s1 += s2;
var d = [...c];
let e = d.filter(function(v, i, self) {
console.log(self.indexOf(v));
return self.indexOf(v) === i;
});
return e;
}
What do we get?
Now let’s log just the index, i
:
What’s the difference? console.log(self.indexOf(v))
is returning the index of the first instance of each value, while console.log(i)
is returning the index of every value.
Specifically, from MDN: “The indexOf()
method returns the first index at which a given element can be found in the array, or -1 if it is not present.”
‘self’
Before going further let’s also establish what ‘self’ is. If I place console.log(self)
within this function the entire array (in our case, d
) is logged to the console 32 times!
Note: if you log self outside the function you’ll get the entire window.
In other words, ‘self’ in this context is the array the filter() method is being called on. In our example, d
:
function longest(s1, s2) {
var c = s1 += s2;
var d = [...c];
let e = d.filter(function(v, i, self) {
console.log(self);
return self.indexOf(v) === i;
});
return e;
}
Again, self.indexOf(v)
is returning the first index at which the value can be found in ‘self’, or the array.
So when filtering through the array, return self.indexOf(v) === i
is returning the value (v)
when the index of the first instance of v equals the index of the current value. Handy!
Step 4:
Now if tack on the sort() and join() methods to the new array, we’ll get our solution! Let’s simultaneously transform it into a ES6 function:
function longest(s1, s2) {
var c = s1 += s2;
var d = [...c];
let e = d.filter((v, i, self) => ( self.indexOf(v) === i ));
return e.sort().join('');
}// Result of calling longest(a,b) in the console:
-> "abcdefklmopqwxy"
Bonus: Simplifying it further
The spread operator has a lot of benefits. One of them is bypassing the need to use the filter method altogether in the above problem! I think it’s important to understand the mechanics of what’s going on beneath the surface, though, so I don’t like to jump to these kind of solutions right away.
What we could do to simplify this further is:
function longest(s1, s2) {
var c = s1 += s2;
var d = [...c];
return [...new Set(d)].sort().join('');
}
What is [...new Set(d)]
doing? Let’s call it to the console:
-> ["x", "y", "a", "b", "c", "d", "e", "f", "w", "k", "l", "m", "o", "p", "q"]
It returns the unique values in a new array!
According to MDN: “The Set
object lets you store unique values of any type…”
To simplify further we can pass c
right into the Set constructor:
function longest(s1, s2) {
var c = s1 += s2;
return [...new Set(c)].sort().join('');
}
Simplifying again we can pass s1 += s2
instead:
function longest(s1, s2) {
return [...new Set(s1 += s2)].sort().join('');
}
Again, transforming it into an arrow function:
const longest = (s1, s2) => ([...new Set(s1 += s2)].sort().join(''));
Beautiful!
More on arrow functions: