简体   繁体   中英

Finding intersection between 2 arrays of objects with dynamic keys

I have 2 arrays of objects with dynamic keys (i never know the name of the key); Example:

hierarchy1: [
  {
    level1: 'Shoes',
  }
]

hierarchy2: [
  {
    level1: 'Shoes',
    level2: 'Sneakers',
  },
]

I need to find intersection between hierarchy1 and hierarchy2. I can't use lodash _.intersectionBy because i dont know the name of the key i will get in hierarchy1 .

I'd expect to get the result like this [{ level1: 'Shoes' }] Any ideas how to solve this issue?

Thanks a lot!

if you want to compare with every index then you can do something like this

 const hierarchy1 = [{ level1: 'Shoes', level3: "xyz" }] const hierarchy2 = [{ level1: 'Shoes', level2: 'Sneakers', }, { level3: "xyz" }] function intersection(arr1, arr2) { let final = [] // loop over first array for (let i = 0; i < arr1.length; i++) { let element = arr1[i] let temp = {} // loop over all indexes of second array for (let data of arr2) { // check every key fro data to see if there's any intersection Object.keys(data).forEach(key => { if (data[key] === element[key] && key in element) { temp[key] = element[key] } }) } // if we found any intersection push it in final array if (Object.keys(temp).length) { final.push(temp) } } return final } console.log(intersection(hierarchy1, hierarchy2))

an improvement to first approach is doing some pre-calculations, you can simply club all the values for a particular key and when looping over you can check if there's particular value present for the given key or not

 const hierarchy1 = [{ level1: 'Shoes', level3: "xyz" },{level2: "abc"}] const hierarchy2 = [{ level1: 'Shoes', level2: 'Sneakers', }, { level3: "xyz", level2: "abc" }] function intersection(arr1, arr2) { let final = [] let map = {} for(let data of arr2){ Object.keys(data).forEach(key => { map[key] = map[key] || new Set() map[key].add(data[key]) }) } // loop over first array for (let i = 0; i < arr1.length; i++) { let element = arr1[i] let temp = {} Object.keys(element).forEach(key => { if (key in map && map[key].has(element[key])) { temp[key] = element[key] } }) // if we found any intersection push it in final array if (Object.keys(temp).length) { final.push(temp) } } return final } console.log(intersection(hierarchy1, hierarchy2))

If you just want to compare respective indexes or both array you can do something like this

 const hierarchy1 = [{ level1: 'Shoes', }] const hierarchy2 = [{ level1: 'Shoes', level2: 'Sneakers', },] function intersection(arr1,arr2){ let final = [] for(let i=0; i<arr1.length; i++){ let element = arr1[i] let temp = {} Object.keys(element).forEach(key => { if(key in arr2[i] && arr2[i][key] === element[key]){ temp[key] = element[key] } }) if(Object.keys(temp).length){ final.push(temp) } } return final } console.log(intersection(hierarchy1,hierarchy2))

You can use _.intersectionWith() that accepts multiple arrays, and a comparator function to compare between two items. To find matching keys, we can use _.intersection() between the keys of the two objects, and then use Array.some() to find at least one key that have matching values in both objects.

 const intersectionWithDynamicKeys = (arr1, arr2) => _.intersectionWith( arr1, arr2, (o1, o2) => _.union(_.keys(o1), _.keys(o2)) .some(key => o1[key] === o2[key]) ) // Finds [{ level1: 'Shoes' }] console.log(intersectionWithDynamicKeys( [{ level1: 'Shoes' }], [{ level1: 'Shoes', level2: 'Sneakers' }] )) // Finds [{ level1: 'Shoes' }] but doesn't find { level2: 'Sneakers' console.log(intersectionWithDynamicKeys( [{ level1: 'Shoes' }, { level2: 'Sneakers' }], [{ level1: 'Shoes', level2: 'Sneakers' }] ))
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

The main caveat using _.intersectionWith() is that as soon as an item matches it's not used again. So if for example you have

const arr1 = [{ level1: 'Shoes' }, { level2: 'Sneakers' }],
const arr2 = [{ level1: 'Shoes', level2: 'Sneakers' }]

only the { level1: 'Shoes' } because { level1: 'Shoes', level2: 'Sneakers' } would not be used to compare with { level2: 'Sneakers' } , since a match was found.

If you want to find all objects with matching keys, you'll need to compare all objects in one array to all objects in the other. To do so, filter the 1st array. Use Array.some() to iterate the 2nd array. Get all unique keys (using a Set ), and then try to find at least one key that has the same value in both objects.

Note : without the match once limit, multiple objects from the 1st array can be matched by a single object in the 2nd.

 const getUniqueKeys = (o1, o2) => [...new Set([...Object.keys(o1), ...Object.keys(o2)])] const intersectionWithDynamicKeys = (arr1, arr2) => arr1.filter(o1 => arr2.some(o2 => getUniqueKeys(o1, o2) .some(key => o1[key] === o2[key]) ) ) // Finds [{ level1: 'Shoes' }, { level2: 'Sneakers' }] console.log(intersectionWithDynamicKeys( [{ level1: 'Shoes' }, { level2: 'Sneakers' }], [{ level1: 'Shoes', level2: 'Sneakers' }] )) // Finds [{ level1: 'Shoes' }, { level2: 'Sneakers' }, { level2: 'Sneakers' }] console.log(intersectionWithDynamicKeys( [{ level1: 'Shoes' }, { level2: 'Sneakers' }, { level2: 'Sneakers' }], [{ level1: 'Shoes', level2: 'Sneakers' }] ))

You can use this to reshape it a tad.

var all = [...hierarchy1, ...hierarchy2].map(c=> Object.entries(c)).flat()
/*
[
  [ 'level1', 'Shoes' ],
  [ 'level1', 'Shoes' ],
  [ 'level2', 'Sneakers' ]
]
*/

Then put it into whatever format you want after that

all.reduce((a,[key,value])=>({
     ...a, 
    [key]: a[key]?a[key]+1:1
  }),{})

/*
{ level1: 2, level2: 1 }
*/

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