Reputation: 169
Why do we need to reduce
the mapped Boolean Value to a single value at the end of the function?
The purpose of the function is to filter an array of objects (first argument) with the key
value
pairs from another object (second argument).
It should return another array of object
with the objects from the first argument which matched both the key
and value
from the second argument.
For example if the first argument is [{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }]
And the second is { last: "Capulet" }
The function should return [{ first: "Tybalt", last: "Capulet" }]
This is the solution I stumbled upon and can't understand how the map(
) and reduce()
work here.
function whatIsInAName(collection, source) {
var srcKeys = Object.keys(source);
return collection.filter(function (obj) {
return srcKeys
.map(function(key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
})
.reduce(function(a, b) {
return a && b;
});
});
}
Thanks!
Upvotes: 3
Views: 2073
Reputation: 18249
Let's start with this function which is used in the solution. It's a predicate which takes one of the keys from the object in source
and returns a Boolean:
function(key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
}
The slightly tricky thing is that it also references obj
, which in context refers to one of the objects in collection
. So you can think of it, at this point in the explanation, as a function of two variables really:
function(obj, key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
}
So when is this true? That's easy: when obj
has key
as one of its keys, and for which the value equals that of source
at the same key. In other words, it's checking that obj
and source
agree as far as the key key
is concerned.
So what do we do with this function? We map
it over the array srcKeys
- which holds all the keys of source
. So, remembering that this is with reference to a fixed object obj
, we get an array of Booleans indicating whether each key of source
is found, with the same value, in obj
.
Then the reduce
operation, which simply uses the &&
operator, just checks whether all the values in this array of Booleans are true. So we see that this section of code:
return srcKeys
.map(function(key) {
return obj.hasOwnProperty(key) && obj[key] === source[key];
})
.reduce(function(a, b) {
return a && b;
});
returns a Boolean checking whether obj
has all the keys specified in source
, and agrees at the values.
The last step is now natural - collection
is filter
ed by this predicate, meaning the final result is an array consisting of those elements of collection
which agree with source
in all keys of source
. Which is exactly what the desired behaviour was :)
Upvotes: 1
Reputation: 522412
First the function creates an array of all keys it needs to look for in srcKeys
, e.g. ['last']
. For the sake of illustrating this algorithm properly, let's assume you're looking for two keys, source = { last: 'Capulet', first: 'Jon' }
→ ['last', 'first']
.
Then it maps that array to an array of booleans, where each value signifies whether the item in collection
has that key and whether its value is the same as in source
. E.g.:
['last', 'first'] → [true, true] // or
['last', 'first'] → [false, false] // or
['last', 'first'] → [false, true] // ...
It then reduces this array of booleans to a single boolean result, which will only be true
if all items in the array are true
:
[true, true] → true
[false, false] → false
[false, true] → false
FWIW, you could do that in one step using Array.prototype.every
:
return collection.filter(obj => {
return srcKeys.every(key => obj.hasOwnProperty(key) && obj[key] === source[key]);
});
Upvotes: 6