0

I have two arrays of objects.

One array contains objects representing users. These objects have two properties, partido and categoria.

A second array contains objects representing collections. These objects may have many properties, which also include the properties partido and categoria.

Question 1

My first question is how do I find objects in the second array that are matches. The object in the second array should be a match if it contains the same properties and values as one of the objects from the first array. The other properties can have any value, so long as partido and categoria match.

The result should be an array containing the matching objects from the second array.

Array 1

const userData = [
  { partido: 'Partido 8', categoria: '2'},
  { partido: 'Partido 13', categoria: '3' }
];

Array 2

collectionData = [
  {
    partido: 'Partido 8',
    Equipo_Local: 'Argentina',
    Equipo_Visitante: 'Arabia Saudí',
    categoria_1: '1',
    categoria: '2',
    Categoria_3: 'No disponible',
  },
  {
    partido: 'Partido 24',
    Equipo_Local: 'Argentina',
    Equipo_Visitante: 'México',
    Categoria_1: 'Disponible',
    categoria: '2',
    Categoria_3: 'disponible',
  },
  {
    partido: 'Partido 13',
    Equipo_Local: 'Polonia',
    Equipo_Visitante: 'Argentina',
    Categoria_1: 'No disponible',
    Categoria_2: 'No disponible',
    categoria: '3',
  },
];

So far I've attempted something like:

function compararObjetos(userData, collectionData) {
  for (let index = 0; index < userData.length; index++) {
    const element = userData[index];
    let keys = Object.keys(element);
    return collectionData.filter(function(obj) {
      for (let i = 0; i < keys.length; i++) {
        if(!obj.hasOwnProperty(keys[i]) || obj[keys[i]] !== element[keys[i]]) {
          return false;
        }
      }
      return true;
    })
  }
}

Question 2

My follow-up question is that I would like to perform the same comparison to check for matches, but instead of getting matches from the second array, get a list of objects from the first array that resulted in a match. This assumes we can have many objects in our first array and some may not result in matches. We just want to return the objects that have matches. What is a good way to do this?

Question 3

Finally, rather than check for a match based on all the properties of the objects in the first array, what if we only need to check that certain properties match?

For instance, the objects can have many other properties but in our case there are 4 specific keys/names that need to have matching values for between the two objects in order to be considered a match.

In my example, I need to return a list of objects from the first array userData that resulted in matches for the keys partido, categoria_1, categoria_2 and categoria_3.

Here is what I tried so far. I think this should be returning matches for Tania and Vania, but I cannot figure out why it's still returning an empty array:

const userData = [
    { user_name: "Tania", email: "[email protected]", partido: "8", categoria_1: "1", categoria_2: "2", categoria_3: null },
    { user_name: "Jean", email: "[email protected]", partido: "12", categoria_1: "1", categoria_2: null, categoria_3: "3" },
    { user_name: "Tom", email: "[email protected]", partido: "39", categoria_1: "1", categoria_2: null, categoria_3: null },
    { user_name: "Vania", email: "[email protected]", partido: "13", categoria_1: null, categoria_2: null, categoria_3: "3" }
  ],
  collectionData = [
    {
      partido: "8",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "Arabia Saudí",
      categoria_1: "1",
      categoria_2: "No",
      categoria_3: "No"
    },
    {
      partido: "24",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "México",
      categoria_1: "No",
      categoria_2: "2",
      categoria_3: "No"
    },
    {
      partido: "13",
      Equipo_Local: "Polonia",
      Equipo_Visitante: "Argentina",
      categoria_1: "No",
      categoria_2: "No",
      categoria_3: "3"
    }
  ];

function compareObjects(first, second) {
  let matches = [];

  second.forEach((possibleMatch) => {
    const pickedCollection = (({ partido, categoria_1, categoria_2, categoria_3 }) => ({ partido, categoria_1, categoria_2, categoria_3 }))(possibleMatch);
  first.forEach((userData) => {
  
      const isMatch = Object.keys(userData).every((key) => {

        return userData[key] === pickedCollection[key];

      });

      if (isMatch && !matches.find((match) => match === userData)) {
        matches.push(userData);
      }
    });
  });

  return matches;
}

console.log(compareObjects(userData, collectionData));

1
  • 1
    Any of your return do an exit function (and ignore loop continuation) Commented Aug 29, 2022 at 16:20

2 Answers 2

1

as I told you in my first comment here:
Any of your return do an exit function (and ignore loop continuation)

you can simply do :
(same answer with code optimization)
use also Array.some() method

function compararObjetos(userData, collectionData)
  {
  let userCollection = []     // future result

  if (userData.length === 0)   // if userData array is empty
    return userCollection     // return an empty array, and exit function

  collectionData.forEach( cRow =>  // roll over each collectionData rows
    {                             // ...and give them 'cRow' as a locale name
    if (userData.some( uRow =>     // roll over each userData rows and
      Object                      // check if one of them is corresponding       
      .keys( uRow )              // get keys names on this userData row
      .reduce( (t,k) => t && (uRow[k]===cRow[k]), true)  // classic reduce...       
      ))                                                // ...(read forward)
      userCollection.push({...cRow}); // true?-> add a cRow copy in response
    })
  return userCollection
  }

// testing...
const
  dataUser = 
    [ { partido: 'Partido 8',  categoria: '2' } 
    , { partido: 'Partido 13', categoria: '3' } 
    ] 
, dataCollection = 
    [ { partido          : 'Partido 8'
      , Equipo_Local     : 'Argentina'
      , Equipo_Visitante : 'Arabia Saudí'
      , categoria_1      : '1'
      , categoria        : '2'
      , Categoria_3      : 'No disponible'
      } 
    , { partido          : 'Partido 24'
      , Equipo_Local     : 'Argentina'
      , Equipo_Visitante : 'México'
      , Categoria_1      : 'Disponible'
      , categoria        : '2'
      , Categoria_3      : 'disponible'
      } 
    , { partido          : 'Partido 13'
      , Equipo_Local     : 'Polonia'
      , Equipo_Visitante : 'Argentina'
      , Categoria_1      : 'No disponible'
      , Categoria_2      : 'No disponible'
      , categoria        : '3'
      } 
    ]
 
console.log( compararObjetos(dataUser, dataCollection) )
.as-console-wrapper {max-height: 100% !important;top: 0;}
.as-console-row::after {display: none !important;}

// previous first answer

function compararObjetos(userData, collectionData)
  {
  let userCollection = []            // future result

  userData.forEach (uRow =>          // roll over each userData rows
    {
    let keys = Object.keys( uRow )   // get keys names on this row
    collectionData.forEach( cRow =>  // roll over each collectionData rows 
      {
      if (keys.reduce((t,k)=> t && (uRow[k]===cRow[k]),true)) // classic reduce usage (read forward)
        userCollection.push({...cRow})                       // add a copy collectionData row to response
      })
    })
  return userCollection
  }

const
  dataUser = 
    [ { partido: 'Partido 8',  categoria: '2' } 
    , { partido: 'Partido 13', categoria: '3' } 
    ] 
, dataCollection = 
    [ { partido          : 'Partido 8'
      , Equipo_Local     : 'Argentina'
      , Equipo_Visitante : 'Arabia Saudí'
      , categoria_1      : '1'
      , categoria        : '2'
      , Categoria_3      : 'No disponible'
      } 
    , { partido          : 'Partido 24'
      , Equipo_Local     : 'Argentina'
      , Equipo_Visitante : 'México'
      , Categoria_1      : 'Disponible'
      , categoria        : '2'
      , Categoria_3      : 'disponible'
      } 
    , { partido          : 'Partido 13'
      , Equipo_Local     : 'Polonia'
      , Equipo_Visitante : 'Argentina'
      , Categoria_1      : 'No disponible'
      , Categoria_2      : 'No disponible'
      , categoria        : '3'
      } 
    ]
 
console.log( compararObjetos(dataUser, dataCollection) )
.as-console-wrapper {max-height: 100% !important;top: 0;}
.as-console-row::after {display: none !important;}

This code work also in this case:

const dataUser = 
    [ { Equipo_Visitante : 'México' } 
    , { partido: 'Partido 13', categoria: '3' } 
    ] 


explanations on:

keys.reduce((t,k)=> t && (uRow[k]===cRow[k]),true)

As you can imagine this Array.reduce() method will return true or false

for the first row the keys values are keys[0]='partido' and keys[1]='categoria'

that's mean we need to perform this test:

(uRow['partido']===cRow['partido'] && uRow['categoria']===cRow['categoria'])

so it is:

(uRow[keys[0]]===cRow[keys[0]] && uRow[keys[1]]===cRow[keys[1]])

as the count of keys elements is unknown this test is done as follows:

let t = true   // t is test initial value
for (let k of keys)
  t = t && (uRow[k]===cRow[k])
return t

which is simply written in an Array.reduce() method:

keys.reduce((t,k)=> t && (uRow[k]===cRow[k]),true)

otherwise you can also change this test to check that there is at least one matching value:

keys.reduce((t,k)=> t || (uRow[k]===cRow[k]),false) 
Sign up to request clarification or add additional context in comments.

6 Comments

Not sure how you can say the OP's code is convoluted and unpleasant to read, and then write a statement like this: if (keys.reduce((t,k)=> t && (uRow[k]===cRow[k]),true))
Also I'm not sure the output is correct. I think it's supposed to give all matches for the first element in userData that contains matches in collectionData - not all matches for all elements in userData.
@OmarGiancarlo Ok, I will do that, but first and second example use exactly the same code...
@OmarGiancarlo I added some explanations, and an optimized answer witch go faster
@OmarGiancarlo there is a tidy button in SO snippet witch do automatic indentation.
|
1

This is how I'd do it. This seems a bit more succinct and declarative to me. An explanation is included below the solution.

Solution

Here is the function:

function compareObjects(first, second) {
  let matches = [];

  second.forEach((possibleMatch) => {
    first.forEach((obj) => {
      const isMatch = Object.keys(obj).every((key) => {
        return obj[key] === possibleMatch[key];
      });
      if (isMatch && !matches.find((match) => match === possibleMatch)) {
        matches.push(possibleMatch);
      }
    });
  });

  return matches;
}

And here is a full working example with your data set:

const userData = [
    { partido: "Partido 8", categoria: "2" },
    // { categoria_1: "1", Equipo_Local: "Argentina" },
    // { categoria_1: "1", Equipo_Local: "Palonia" },
    { partido: "Partido 13", categoria: "3" }
  ],
  collectionData = [
    {
      partido: "Partido 8",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "Arabia Saudí",
      categoria_1: "1",
      categoria: "2",
      Categoria_3: "No disponible"
    },
    // {
    //   partido: "Partido 8",
    //   Equipo_Local: "Palonia",
    //   Equipo_Visitante: "Argentina",
    //   categoria_1: "1",
    //   categoria: "2",
    //   Categoria_3: "No disponible"
    // },
    {
      partido: "Partido 24",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "México",
      Categoria_1: "Disponible",
      categoria: "2",
      Categoria_3: "disponible"
    },
    {
      partido: "Partido 13",
      Equipo_Local: "Polonia",
      Equipo_Visitante: "Argentina",
      Categoria_1: "No disponible",
      Categoria_2: "No disponible",
      categoria: "3"
    }
  ];

/**
 *
 * @param {Object[]} first
 * @param {Object[]} second
 *
 * A function that takes in two arrays of objects and
 * returns an array of matching objects from the second
 * array, where a match is defined as an object from the
 * second array which, when compared against the objects
 * in first array, contains the same entries (the same key/value pairs
 * are present in the object in the second array) as at least
 * one of the objects.
 *
 */
function compareObjects(first, second) {
  let matches = [];

  second.forEach((possibleMatch) => {
    first.forEach((obj) => {
      const isMatch = Object.keys(obj).every((key) => {
        return obj[key] === possibleMatch[key];
      });
      if (isMatch && !matches.find((match) => match === possibleMatch)) {
        matches.push(possibleMatch);
      }
    });
  });

  return matches;
}

console.log(compareObjects(userData, collectionData));

https://codesandbox.io/s/get-matches-two-arrays-of-objects-59wcoi

Explanation

We start by looping over the thing we want to return, the collections. For each collection, we loop over all the users, and when comparing against each user, we check if every entry in the user object is present in the collection.

If it's a new match, we add it.

I've generalized the solution to return matches from the second arg comparing against the first so it can be more useful to others.

I've also commented out additional objects supplied in the arguments to illustrate the line of inquiry in my reply about defining assumptions. This relates to why I included a condition to check for previous matches. You can simply drop that bit if needed.

[Edited to include latest questions from OP]

Get Objects From First Array That Resulted in a Match

To return objects from the first array that resulted in matches, rather than the matches found in the second, you could do this several ways.

We could move the first array to the outer loop from our first solution. Then, we still make the same comparison to check for matches, but now we push the matching object from the first array rather than the second.

https://codesandbox.io/s/get-first-matches-two-arrays-of-objects-0bovhw

const userData = [
    { partido: "Partido 8", categoria: "2" },
    { partido: "Partido 13", categoria: "3" },
    // these won't be matches
    { categoria_1: "28", Equipo_Local: "Argentina" },
    { categoria_1: "1", Equipo_Local: "Palonia" }
  ],
  collectionData = [
    {
      partido: "Partido 8",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "Arabia Saudí",
      categoria_1: "1",
      categoria: "2",
      Categoria_3: "No disponible"
    },
    {
      partido: "Partido 24",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "México",
      Categoria_1: "Disponible",
      categoria: "2",
      Categoria_3: "disponible"
    },
    {
      partido: "Partido 13",
      Equipo_Local: "Polonia",
      Equipo_Visitante: "Argentina",
      Categoria_1: "No disponible",
      Categoria_2: "No disponible",
      categoria: "3"
    }
  ];

/**
 *
 * @param {Object[]} first
 * @param {Object[]} second
 *
 * A function that takes in two arrays of objects and
 * returns an array of matching objects from the first
 * array, where a match is defined as an object from the
 * first array whose entries (key/value pairs), when
 * compared against the objects in second array, are all
 * present in at least one of objects in the second array.
 */
function getFirstMatches(first, second) {
  let matches = [];

  first.forEach((possibleMatch) => {
    second.forEach((obj) => {
      const isMatch = Object.keys(possibleMatch).every((key) => {
        return obj[key] === possibleMatch[key];
      });
      if (isMatch && !matches.find((match) => match === possibleMatch)) {
        matches.push(possibleMatch);
      }
    });
  });

  return matches;
}

console.log(getFirstMatches(userData, collectionData));

Get Objects From First Array That Resulted in a Match Based On Only Specific Keys

To get matches based on only specific keys you could just check against every one of those keys rather than all the keys of the object.

https://codesandbox.io/s/matches-array-of-objects-specific-keys-16k7gg

const userData = [
    {
      user_name: "Tania",
      email: "[email protected]",
      partido: "8",
      categoria_1: "1",
      categoria_2: "2",
      categoria_3: "No"
    },
    {
      user_name: "Jean",
      email: "[email protected]",
      partido: "12",
      categoria_1: "1",
      categoria_2: "No",
      categoria_3: "3"
    },
    {
      user_name: "Tom",
      email: "[email protected]",
      partido: "39",
      categoria_1: "1",
      categoria_2: "No",
      categoria_3: "No"
    },
    {
      user_name: "Vania",
      email: "[email protected]",
      partido: "13",
      categoria_1: "No",
      categoria_2: "No",
      categoria_3: "3"
    }
  ],
  collectionData = [
    {
      partido: "8",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "Arabia Saudí",
      categoria_1: "1",
      categoria_2: "No",
      categoria_3: "No"
    },
    {
      partido: "24",
      Equipo_Local: "Argentina",
      Equipo_Visitante: "México",
      categoria_1: "No",
      categoria_2: "2",
      categoria_3: "No"
    },
    {
      partido: "13",
      Equipo_Local: "Polonia",
      Equipo_Visitante: "Argentina",
      categoria_1: "No",
      categoria_2: "No",
      categoria_3: "3"
    }
  ];

const checkedKeys = ["partido", "categoria_1", "categoria_2", "categoria_3"];

/**
*
* @param {Object[]} first
* @param {Object[]} second
*
* A function that takes in two arrays of objects and
* a specific set of keys to check against and returns 
* an array of matching objects from the first
* array, where a match is defined as an object from the
* first array whose entries for the specified keys match
* those in at least one object the second array.
*/
function getFirstMatches(first, second, checkedKeys) {
  let matches = [];

  first.forEach((possibleMatch) => {
    second.forEach((obj) => {
      const isMatch = checkedKeys.every((key) => {
        return obj[key] === possibleMatch[key];
      });
      if (isMatch && !matches.find((match) => match === possibleMatch)) {
        matches.push(possibleMatch);
      }
    });
  });

  return matches;
}

console.log(getFirstMatches(userData, collectionData, checkedKeys));

17 Comments

Okay, that's a quick fix. We can use every() to check each collection against all the key value pairs for the current user object. Please see my updated answer.
It does help to be very explicit about how you define a match. The term match is used a bit loosely in the question but never explicitly defined. It’s also not assumed that there should be a 1:1 or 1:many relationship between user and collection. In any case, hopefully this satisfies the logic you require. Let me know if you have any more questions or if I can be of any further help.
Have you given my updated solution a try? Did I capture the logic you require? Hopefully my comments and questions made sense.
Based on the information you've provided, I can only assume you are asking for something like this: https://codesandbox.io/s/get-first-matches-two-arrays-of-objects-0bovhw. This solution uses the very same logic for determining matches between the two arrays, but returns objects from the first array that resulted in a match rather than the second.
Let me know if this addresses your question, and I'll incorporate it into my answer. Also, if my answer helped you solve your original question, feel free to mark it as an accepted/useful answer :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.