Stuck on Wherefore Art Thou Challenge


#1

Been stuck on this one for a few days. So far, I’ve solved 3/4. Can’t seem to get the final one. Any guidance is much appreciated:

function whatIsInAName(collection, source) {
  // What's in a name?
  var arr = [];
//   // Only change code below this line
  var sourceKeys = Object.keys(source);
  var h;
  
  sourceKeys.filter(function(x) {
    h = x;
    return h;
  });

  collection.filter(function(e) {
    if(e.hasOwnProperty(h)) {
        arr.push(e);
    } 
    
    });
  
  
  

  // Only change code above this line
  return arr;
}

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


#2

That’s not how you use filter (it’s kinda working accidentally atm). First filter:

sourceKeys.filter(function(x) {
    h = x;
    return h;
});

So sourceKeys is an array of keys (strings). A non-empty string is always coerced to true, so the first filter goes through each key, and returns true for it, so you get exactly the same array of keys out the other end. You aren’t saving the result anywhere (filter creates a new array), so it might as well not be there. However you’re assigning to h on each value. Once you’ve gone through that array, h will end up being assigned the last object key present. So this is why it works for some things: if you have a single key - { last: "Capulet" }, h will be last. If you have more than one, h will be the last key - for { first: "Tybalt", last: "Capulet" }, h will probably be last (can’t guarantee it, but probably).

Second filter:

 collection.filter(function(e) {
    if(e.hasOwnProperty(h)) {
        arr.push(e);
    }    
    });

So on this, you’re checking if each collection item has one property in the source (h). Again, not using filter correctly (forEach would be what you use for side effects).


If you’re keeping the arr = []; return arr; bit, and pushing to array, it maybe makes more sense to use forEach or a loop, whereas if you’re using filter, you can just return collection.filter(function dostuff() {}).

  • With filter, you normally would be using it to take collection and filter out the items that don’t match - this gives you a new resultant array.
  • With the loop approach, you are going through collection, picking the ones that do match and adding them to arr.

If you mix the two approaches it gets a bit complicated, like here.

Using Object.keys is fine, but you need to check every key in source, at the minute you’re just checking one


#3

Thanks for your reply @DanCouper.

After doing some more research, this is the code I’ve come up with:

function whatIsInAName(collection, source) {
  // What's in a name?
  var arr = [];
  // Only change code below this line
  var sourceKeys = Object.keys(source);  
 
  
  collection.forEach(function(key) {
     Object.keys(key).forEach(function(k) {
       sourceKeys.forEach(function(s) {
         if(key.hasOwnProperty(s) && source[s] == key[k]) {
           arr.push(key);
         } 
         
       });
     });
  });
   
  // Only change code above this line
  return arr;
}

It works for the first 2/4 answers. If I understand correctly, we need to check if each element in collection has the same key/value pair of source. I think my code correctly checks the values and keys, but I’m having a hard time pushing the specific elements from collection that match the key/value of source. Does this sound like it’s more on the mark? (p.s. sorry for the late reply, I had to catch a flight and get settled. I appreciate your response from before).


#4

With your code above consider this scenario:

whatIsInAName([{ "a": 1 }, { "a": 1, "b": 2, "c": 2 }, { "a": 1, "b": 2 }], { "a": 1, "b": 2 });

when the code reaches:

sourceKeys.forEach(function(s) {
         if(key.hasOwnProperty(s) && source[s] == key[k]) {
           arr.push(key);
         } 

for the first time:

s: a
key: { "a": 1 }
source: { "a": 1, "b": 2 }

Result: The if statement evaluates as true and the key object is added to the arr array, even though it should have never been added, since key: { "a": 1 } is not a proper match of the source: { "a": 1, "b": 2 }, since the key "b" is missing from the key object.

Even when:

key: { "a": 1, "b": 2 }
source: { "a": 1, "b": 2 }

You’re going to push the key object to the arr array two times, because the if statement will evaluate to true for each of the keys, i.e., a and b so the key object gets pushed to arr array for each of the keys.


Problem: You’re pushing the key object to the arr array for each key and value pair in the key object that matches the corresponding key and value pair in the source object.

What’s required to be done: You should push the key object to the arr array only when all the keys of the source object are present in the key object, and each of the keys have same corresponding values in both the objects.



#5

I agree with @pranav-kural’s approach. I had this same issue with duplicates and I was stuck to on this for a bit. I solved it by dissecting the last sentence of the problem – “Each property and value pair of the source object has to be present in the object from the collection if it is to be included in the returned array”.

My approach did exactly that: add the element from collection ONLY if it matched all of the source key: value pairs. That way each collection item is evaluated exactly ONCE and added if there is a of all source key: value pairs are present or excluded if not.

I found this MUCH easier than building an array and then to try and remove/filter from the existing collection if it failed one of the tests. This approach is more intuitive as a human, but seemed more complicated to code. In either case, the end result should be the same, but my approach eliminates the need for reduce/filter and at least for me reduces the logical complexity of the problem.


#6

Thank you @pranav-kural and @islandrob for your responses.

I’m still trying to wrap my head around how to code the approach you both suggest. I’m having trouble comparing the keys. I don’t get how to evaluate the keys in source only once when they are inside the loop.

function whatIsInAName(collection, source) {
  // What's in a name?
  var arr = [];
  // Only change code below this line
  var sourceKeys = Object.keys(source);
  collection.forEach(function(key) {
    if(key[sourceKeys] === source[sourceKeys] && Object.keys(key) === sourceKeys) {
        arr.push(key);
      }    
  });
  
  // Only change code above this line
  return arr;
}

#7

@jawaka72 That’s completely fine. That’s what it is all about, problem solving.

Hint: how about a loop inside the loop :wink:

collection.forEach(function(key) {

    // key object represent one object from collection
    // Now:

    // FOR EACH key of SOURCE object
    // Check if the 'key' object has that key AND the same value for that key
    // if TRUE; do something
    // if false; do something else
    // finish this FOR EACH (or FOR)

    // if all the keys of source found a match in the 'key' object and the values are same too
    // i.e., each loop of SOURCE FOR EACH (or FOR) above evaluated to TRUE
    // Then add the 'key' object to 'arr' array
    // Now go for the next 'key' object

  });

Take one step at a time. First make sure the 'key' object is a proper match (have all key:value pair of source object). Then the second step is to add the 'key' object to the 'arr' object if it successfully passes the first step. Don’t combine both the steps.

This is just a very stripped down pseudocode of how this can be handled, you can mold it to your own way or choose to do something completely different, it’s upto you how you actually program it.


#8
  1. Object.keys returns an array. That array may have multiple elements. You need to account for that in the tests.

  2. I believe that the Object.prototype.hasOwnProperty() hint in the problem is to check if the key is present before you try to access its value. I’m not sure if there will be an error/warning if you access try to access a value that doesn’t exist.

  3. Without giving too much of my solution away, think about it this way. When analyzing each item in the collection, you need to look at each key in source and (1) figure out if the key is in the element from collection and (2) make sure that its corresponding value is the same as that in the source. If this test is met for ALL of the keys in source, then you can add that item from collection into the return array.


#9

As an aside, you can either (1) start with not including the object and then include it if the object passes all tests or (2) assume you will include the object and then eliminate it if it fails any of the tests. I found the latter easier to code.


#10

@pranav-kural and @islandrob
Thank you both so much for your guidance and patience. It’s working now!

Solution:

function whatIsInAName(collection, source) {
  // What's in a name?
  var arr = [];
  // Only change code below this line
  var sourceKeys = Object.keys(source);
  var hasKey;
  collection.forEach(function(key) {
    
    sourceKeys.forEach(function(s) {
      if(key.hasOwnProperty(s) && key[s] === source[s]) {
        hasKey = true;
      } else {
        hasKey = false;
      }  
    });
    
    if(hasKey === true) {
      arr.push(key);
    }
    
  });
  // Only change code above this line
  return arr;
}

I think I was trying to do too much at once and wasn’t breaking down the problem enough. Couldn’t have done it without both of your support. Thanks again!


#11

No prob. I do think your solution has an error that I have also seen in others.
Consider this case: whatIsInAName([{ “a”: 1, “b”: 2 }, { “a”: 1 }, { “a”: 1, “b”: 2, “c”: 2 }], { “a”: 100, “c”: 2 }) ;
Should return [].