Peter Peach
Peter Peach

Reputation: 169

Why do we need to reduce the mapped Boolean values to a single value in a function?

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

Answers (2)

Robin Zigmond
Robin Zigmond

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

deceze
deceze

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

Related Questions