简体   繁体   中英

Using map with reduce in javascript to filter objects in an array

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 ie 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!

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; } 

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM