Wherefore art thou (Object.values + Object.keys)

Tell us what’s happening:

how to include the values comparison into my filter test?

The general idea is to check properties present in the 2 objects (done) and then check the values, so the test just prompts true for those objects in collection having { last: "Capulet" }If I uncomment the last line in the filter function I got that i is not defined (I kept that line just to convey the idea).

Suggestions are welcome!

Your code so far


function whatIsInAName(collection, source) {
var arr = [];
// Only change code below this line
const collectionValues = collection.map(item => Object.values(item))

arr = collection.filter(obj =>
obj.hasOwnProperty(Object.keys(source)))
  //&& Object.values(collectionValues[i]).indexOf(Object.values(source))



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

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

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 OPR/67.0.3575.79.

Challenge: Wherefore art thou

Link to the challenge:

Maybe use
key: value ?
Filter()?
Use a loop to loop through the keys?
Hope this helps

I suggest to loop over the array of objects and if the array element (aka the object) has the key === source.key you push it into a new array.

Maybe this will help myObj.hasOwnProperty('key')

Thanks Kitty… but not that much :frowning:

Kiam thank you. This is an interesting idea. Could you please put an example?
:grin:

So for the first part of the exercise you could use something like below. But it will not pass the test for the last two tests

function whatIsInAName(collection, source) {
var arr = [];
// Only change code below this line
collection.forEach(namePair => {
	if(namePair.last === source.last) {
  	arr.push(namePair)
  }
}) 
return arr;
}

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

For starters

hasOwnProperty takes a single string as an argument. It checks for one property, not an array or them. However, do you think you ever actually need that check?

TBH, you have the correct bits, but you’re overcomplicating, and aren’t putting them together correctly.

Think about what you need here. I’m going to repeat things you’ve already described trying to solve in this and other threads – I’m just trying to write this all in order, complete, I think you’ve been tying yourself in knots over this over a number of threads:

  • The input is an array of objects (collection) and a reference object (source).
  • The output is a filtered version of collection
  • So logically, the return value can be the result of applying filter to collection. You can return this directly or assign to intermediate variables, whatever makes the most sense to you:
    function whatIsInAName(collection, source) {
      // you're gonna need this for every item in the collection,
      // so makes sense to pull it out:
      const sourceKeys = Object.keys(source);
      // then the returns value is whatever you get from filtering:
      return collection.filter(item => /* do something */);
    }
    
  • The filter should return true if the current object being inspected contains the keys and values in the source object.
  • So you need a function that takes an object to inspect (current) and a reference object (source, but note I’ve just pulled out the keys straight away, as sourceKeys), and checks that
    1. for every key in sourceKeys
    2. current[key] is the same value as source[key]
  • Do you actually need anything else? Is there any other step that would be necessary here?

The last step would be inspect the values:

first I made up this:

function whatIsInAName(collection, source) {

  const sourceKeys = Object.keys(source);
  return collection.filter(item => item[sourceKeys] == source[sourceKeys]);
}

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

This promts this error: Cannot convert undefined or null to object

then it was this:

function whatIsInAName(collection, source) {
  const arr = [];
  const sourceKeys = Object.keys(source);
  collection.filter(function(item){
    for (let prop in item){
      if (Object.values(prop) == Object.values(source[sourceKeys])){
        arr.push(item)
      }
    }
  });
  return arr;
}

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

same error

… be patient Dan
I’m a :turtle:

Object.values(source[sourceKeys])

sourceKeys is an array. source is an object. Object.values expects an object to be passed to it. When you write:

source[sourceKeys]

sourceKeys gets coerced into a string, because when you reference a property of an object as a variable (as you have done), the property name must be a string.

Also, is prop an object? Object.values creates an array of property values. You are attempting to compare two arrays that will always result in a false condition. Why? Because variables which are assigned arrays and objects, are actually assigned the arrays or objects. They are assigned a reference to the actual array or object in memory. These will be different memory locations, so they will not be equal. To compare one array to another, you can only check if they have the same elements. This would involve iterating through each element of both arrays and check if the elements are the same.

So a few things for starters: I’ll simplify this template further, but it has the structure that should allow you to solve this. You don’t need Object.values here, and tbh you don’t need Object.keys either (although it can make it a bit nicer to iterate).

I think you’re misunderstanding how filter works. It isn’t a generic loop, you don’t use push with it.

The function you give to filter should return true/false depending on some condition. That’s all: you don’t need to manually build an array: filter will build it for you, filling it with the values that pass the test defined by the callback function (ie they return true), and skipping those that fail (ie they return false).

function whatIsInAName(collection, source) {
  return collection.filter(currentItemIsAMatch);
}

currentItemIsAMatch is the placeholder for the thing you need here: it has to be a function that accepts the current item in the array (an object), and returns true or false by comparing it with the source object.

I’m going to use concrete values as well: this is what I’ll use:

collection = [{ "apple": 1, "bat": 2 }, { "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }]
source = { "apple": 1, "bat": 2 }

So this can’t work:

item[sourceKeys] == source[sourceKeys]

item is the current value in the array. Subsitute in the values. The first item is the object { "apple": 1, "bat": 2 }. So you’re writing:

{ "apple": 1, "bat": 2 }[["apple", "bat"]] == { "apple": 1, "bat": 2 }[["apple", "bat"]] 

That doesn’t look right.

The function should take each one of those objects in collection and return true or false:

{ "apple": 1, "bat": 2 } // true
{ "bat": 2 } // false
{ "apple": 1, "bat": 2, "cookie": 2 } // true

Why though? Well

item = { "apple": 1, "bat": 2 }
source = { "apple": 1, "bat": 2 }
// true because item.apple === source.apple and item.bat === source.bat
item = { "apple": 1, "bat": 2 }
source = { "bat": 2 }
// false because item.apple !== source.apple even though item.bat === source.bat
item = { "apple": 1, "bat": 2 }
source = { "apple": 1, "bat": 2, "cookie": 2  }
// true because item.apple === source.apple and item.bat === source.bat

So to use the dummy name I’ve given for the filter callback, if the function is ran on its own:

currentItemIsAMatch({ "apple": 1, "bat": 2 }) // true
currentItemIsAMatch({ "bat": 2 }) // false
currentItemIsAMatch({ "apple": 1, "bat": 2, "cookie": 2 }) // true

Because, to use pseudocode:

FUNCTION currentItemIsAMatch(item):
  FOR EACH key IN source:
    IF source[key] IS EQUAL TO item[key]:
      # it's a match, keep going
      CONTINUE
    ELSE IF source[key] IS NOT EQUAL TO item[key]:
      # the key/value isn't in the item, fail the test
      RETURN false
    ENDIF
  ENDFOR
  # we've got to the end of the loop, so the test passed
  RETURN true

Which can be rewritten to return as soon as the test fails:

FUNCTION currentItemIsAMatch(item):
  FOR EACH key IN source:
    IF source[key] IS NOT EQUAL TO item[key]:
      # the key/value isn't in the item, fail the test immediately
      RETURN false
    ENDIF
  ENDFOR
  # we've got to the end of the loop, so the test passed
  RETURN true

So for currentItemIsAMatch({ "apple": 1, "bat": 2 }), I loop over source.

  • source["apple"] is equal to item["apple"]
  • continue
  • source["bat"] is equal to item["bat"]
  • continue
  • loop has finished, return true

So for currentItemIsAMatch({ "bat": 2 }), loop over source:

  • source["apple"] is not equal to item["apple"] (latter is undefined)
  • return false

So for currentItemIsAMatch({ "apple": 1, "bat": 2, "cookie": 2 }), loop over source:

  • source["apple"] is equal to item["apple"]
  • continue
  • source["bat"] is equal to item["bat"]
  • continue
  • loop has finished, return true

So the callback returns true for the first and third items:

collection.filter(currentItemIsAMatch)

Will resolve to [{ "apple": 1, "bat": 2 }, { "apple": 1, "bat": 2, "cookie": 2 }]


NOTE that this is only one way of doing it, there are many other approache that you can use (and should investigate). For example, it could be similar to above, but use Object.keys and the Array.prototype.every method.

Thanks Randell and Dan!!

Still I don’t have a positive answer, but I have 2 questions: why am I getting undefined (therefore, an empty array) when the following code return true for the value I want to return true:

function whatIsInAName(collection, source) {
  var arr = [];
  
  const sourceKeys = Object.keys(source);

  arr = collection.filter(function(obj){
    sourceKeys.forEach(function(key){
      if (source[key] !== obj[key]){
        return false
      }
      return true
    })
  })
  return arr;
}

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

and why happens the same (return true but then undefined) with:

function whatIsInAName(collection, source) {
  var arr = [];
  
  const sourceKeys = Object.keys(source);

  arr = collection.filter(function(obj){
    sourceKeys.forEach(function(key){
      if (key in obj){
        return true
      }
      return false
    })
  })
  return arr;
}

In both functions posted above, thefilter method’s callback function never explicitly returns a value of true or false. The return statements inside of the forEach method’s callback function will have no impact on what the filter method’s callback returns. Since the filter method’s callback does not return a value, the value undefined gets returned which is “falsy”, so the filter method does not keep any of the collection elements. That is why you end up with an empty array [ ].

Also, you might want to add console.log(key) inside the forEach method’s callback. sourceKeys is an array, so key will be a number (0 or higher) representing the index of the array. I am sure that is not what you are trying to do. Maybe you should explain your algorithm to us and then we can help guide you to the code you will need to write to execute the algorithm.

Ok, this is the simplest algorithm I can see here:
Since my point of view, the problem is that I don’t know how to script the step TEST:

so
step 1 filter collection
step 2 set a filter TEST to iterate over collection objects and keep those whose name property and value are the same of source (here, the scripting problem)
step 3 return the filtered collection

function whatIsInAName(collection, source) {
  var arr = [];

  return arr.collection(function(obj){
    //I know I can't compare two objects with == or === because they are in differents places in memory. So, I want to check if the names assigned to the properties and values in each collection item are literally the same in the props : values pairs in source. And I know that the way to access into a value is collection[i][key] and source[key], cause collection is an array of objects and source an object.

    if(TEST){
      return true;
    }
  })
}

Try to break this step into smaller pieces. Think about how you would do this manually without a computer.

iterate over collection would be like:
[0] {prop: val}
[1] {prop1: val1}
etc.

set aside each prop: value pair in source (for… in comes to my mind)
prop1: val 1
prop2: val2

test if [each in collection] is like prop1: val1 or any other pair
for example: {prop: val} is actually like [0] {prop: val}
keep it and go on

Hi there :wave:
I have something that I just can’t get my head around. Please forgive me. I’m sure the answer is perfectly obvious but it’s completely evading me.

We create a variable sourceKeys

which is an array of keys (property names):
sourceKeys = [ 'apple', 'bat' ] for instance
This is clear at least :slight_smile:

However, this is where I walk into thick fog.

So you need a function that takes an object to inspect ( current ) and a reference object (source)

Yep, I’m still with it so far!

  • for every key in sourceKeys

Yep!

  • current[key] Yep! No prob’s.

is the same value as source[key]

I’m falling over now. Do you mean the current value of
> sourceKeys (the array of keys that we created)? Or a value directly from the input source ?
{ "apple": 1, "bat": 2 } for instance. This is one point of foggyness.

What I really can’t see is a mention of testing the values belonging to the keys (1 & 2) for example. Am I just missing the blindingly obvious here?

Thanks.
LT

I’m only testing the values, nothing else.

  • If source is { "apple": 1, "bat": 2 }
  • then the keys for source are "apple" and "bat"
  • so , iterating, the value for key is "apple" and then it is "bat"
  • so source[key] is first source.apple ie the number 1, then is source.bat, ie the number 2.

So for every object in collection, you iterate over source, and for each property, test whether the value for that key equals the value for current[thatSameKey]

Yeah! Thank you for clarifying this.

Did you mean to write source.bat, ie the number 2

1 Like

Yes I did! Thank you, edited

No! Thank you, this is totaly clear now and I will be able to progress.

Cheers,
LT