1

There is a question from freecodecamp and detail are as follows:

Requirement:
Make a function that looks through an array of objects (first argument) and returns an array of all objects that have matching name and value pairs (second argument).

For example, if the first argument is [{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], and the second argument is { last: "Capulet" }, then you must return the third object from the array (the first argument), because it contains the name and its value, that was passed on as the second argument.

Expected outcome:

whatIsInAName([{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], { last: "Capulet" }) should return [{ first: "Tybalt", last: "Capulet" }].

whatIsInAName([{ "apple": 1, "bat": 2 }, { "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "bat": 2 }) should return [{ "apple": 1, "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }].

whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "cookie": 2 }) should return [{ "apple": 1, "bat": 2, "cookie": 2 }].

whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }, { "bat":2 }], { "apple": 1, "bat": 2 }) should return [{ "apple": 1, "bat": 2 }, { "apple": 1, "bat": 2, "cookie":2 }].

And according to the site, there is an solution like this:

function whatIsInAName(collection, source) {
  var srcKeys = Object.keys(source);

  // filter the collection
  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;
      });
  });
}

// test here
whatIsInAName([{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }], { last: "Capulet" });

In this solution, there is one thing that I am not quite understanding, which is the return value from the map function.

Before, in my expectation, the map function will loop through all the key and value pairs to check if it matches or not, and return the array with boolean value i.e sth like [{true, false}, {false, false}] etc, and pass the boolean value to the reduce function.

However, when I tested the map function with the following script:

var source = { last: "Capulet" };
var collection = [{ first: "Romeo", last: "Montague" }, { first: "Mercutio", last: null }, { first: "Tybalt", last: "Capulet" }];
var srcKeys = Object.keys({ last: "Capulet" });

collection.filter(function(obj){
  return srcKeys.map(function(key){
    return obj.hasOwnProperty(key) && obj[key] === source[key];
  })
})

And the return is like this

(3) [{…}, {…}, {…}]
 0: {first: "Romeo", last: "Montague"}
 1: {first: "Mercutio", last: null}
 2: {first: "Tybalt", last: "Capulet"}
 length: 3
  __proto__: Array(0)

In this case, I have 2 questions:

  1. In the map function, it creates a new array with the results of calling a provided function on every element in the calling array. In this case, as we want to return only elements which match the conditions, why didn't it return a boolean value or only elements with matched value, but returns all the values? Or do I have sth that I understood wrong about the mapping function?

  2. In the reduce function which after the map function, How can it obtain the mapped Boolean values to a single Boolean that indicates whether all srcKeys pass the conditions checked above? For example, in this case, does the reduce function simply take the return value of map and compute further?

Many thanks for the help in advance!

Ele
  • 33,468
  • 7
  • 37
  • 75
Pak Hang Leung
  • 389
  • 5
  • 15
  • 1
    **Hint:** absolutely you don't need the function `map`. – Ele Sep 23 '18 at 14:18
  • 1
    In example 1, the return value of the map function is an array of true/false values depending on whether the current object passed each test. Then the reduce function ANDs all those true/false values together, producing true (yes, this element passed all the conditions) or false (it failed one or more). The .filter function operates on that final boolean. In your test you are seeing the output of the .filter operation not the .map – James Sep 23 '18 at 14:23
  • 1
    Replacing last sentence of prev comment. In your test, the function inside .filter returns an array of true/false values instead of logically converting them to a single true/false value (which is what the .reduce did) – James Sep 23 '18 at 14:48
  • @James Thanks! And may I clarify, that means in this case, although the map function did not return an array with boolean values in console log, the result returned already have a implicited array with booleans value inside, and can be used further with reduce? – Pak Hang Leung Sep 23 '18 at 15:55

2 Answers2

2

The map part maps all key-value pairs of source to an array of booleans that indicate wether the value is in the obj:

 var obj = { a: 1, c: 3 };
 var source = { a: 1, b: 2, c: 3 };

 var mapped = Object.keys(source).map(key => obj[key] === source[key]);

 console.log(mapped); // [true, false, true]

Now that array can't be used as the return value to filter directly, because arrays are always truthy, no matter whats insode them. Now the reducer turns that array into one boolean, the following:

 [true, false, true].reduce((a, b) => a && b) // false

Is the same as:

 true && false && true // false

So at the end it returns true if all key-values are existing.


PS: stop this course, the proposed code is awful.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Thanks and it exactly replies my doubt =) PS: I also aware of this, but seems to be a good way to at least understand how things works even the code is not the best practise, that why still reading it haha =) – Pak Hang Leung Sep 23 '18 at 15:59
  • And just one more question: Because I also expected that the result for the map will be similar with the var mapped, but turns out it return the whole string. Can I know why is that? – Pak Hang Leung Sep 23 '18 at 16:06
1

As I said, the function map is not necessary.

You can use the function filter along with the function every in order to filter those objects which match with the key-value pair object (second param).

let whatIsInAName = (arr, obj) => arr.filter(o => Object.keys(obj).every(k => obj[k] === o[k]));

console.log(whatIsInAName([{ "apple": 1, "bat": 2 }, { "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "bat": 2 }));
console.log(whatIsInAName([{ "first": "Romeo", "last": "Montague" }, { "first": "Mercutio", "last": null }, { "first": "Tybalt", "last": "Capulet" }], { "last": "Capulet" }));
console.log(whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }], { "apple": 1, "cookie": 2 }));
console.log(whatIsInAName([{ "apple": 1, "bat": 2 }, { "apple": 1 }, { "apple": 1, "bat": 2, "cookie": 2 }, { "bat":2 }], { "apple": 1, "bat": 2 }));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Ele
  • 33,468
  • 7
  • 37
  • 75
  • Thanks! Actually this methods works better but just want to clarifty some concepts that I may not understand, since I am still a beginner in this =) – Pak Hang Leung Sep 23 '18 at 15:57