-1

I have two deal with two Set instances.

const set1 = new Set([
  { name: 'a' },
  { name: 'b', lastname: 'bb' },
  { name: 'c' },
  { name: 'd' },
]);

const set2 = new Set([
  { name: 'b' },
  { name: 'd' },
]);

Any object within a set will feature several and also distinct keys and values. The goal is to find structurally equal objects (same keys and values) in both sets, which is ... The intersection of equal data items in/of set1 and set2.

In the following example the expected result is [ { name: 'd' } ] ...

console.log([...set1].filter(item => set2.has(item)));

... but it logs an empty array / [] instead.

An object features more than 20 keys so one has to compare them one by one, which can not be done in a hard coded way.

How could one achieve a generic approach for an intersection of two lists of structurally equal data items?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Atousa Darabi
  • 847
  • 1
  • 7
  • 26
  • 3
    Even though the objects appear to be the same (read "same content"), they are not the same (read physical location in memory). So likely you will have to do a manual comparison of the (relevant) properties. – Sirko Feb 07 '22 at 08:25
  • @Sirko or for simple objects compare their `JSON.stringify()`s – Luca Kiebel Feb 07 '22 at 08:26
  • https://stackoverflow.com/questions/1068834/object-comparison-in-javascript I think it should help you – Kordrad Feb 07 '22 at 08:30
  • [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#instance_methods) doesn't have a `filter` method. Is there a reason you are using `Set`s ? – Titus Feb 07 '22 at 08:32
  • @Titus it is in my source code and I can't changed it. I just need to add a component and now need it to be filtered. – Atousa Darabi Feb 07 '22 at 08:39
  • Maybe the following thread helps in finding an approach/solution ... [_For an array of JSON conform object literals, how does one compare the equality of such items?_](https://stackoverflow.com/questions/66155485/for-an-array-of-json-conform-object-literals-how-does-one-compare-the-equality) – Peter Seliger Feb 07 '22 at 08:56
  • @AtousaDarabi ... Are the data structures which are going to be compared to one another always as flat as shown with the example code or is there the possibility of nested data structures as well? The latter case needs a generic (most probably recursive) approach of detecting deep data structure equality. Regardless of the comparison approach the overall task can be summarized with ... _"How does one achieve the **intersection of two lists** / arrays / sets"_ – Peter Seliger Feb 07 '22 at 11:52
  • @PeterSeliger I really appreciate your comments and answer, but unfortunately I couldn't use it. It was so complicated. In fact I discussed with our team and changed the idea. Sorry for late answer though. – Atousa Darabi Mar 28 '22 at 14:31
  • @PeterSeliger By the way, as you edited the question, it didn't convey my exact meaning... . I believe asking in simple way is more useful for who are not so familiar to English. Thanks for your time and effort. – Atousa Darabi Mar 28 '22 at 14:35

3 Answers3

0

You can do something like this:

const set1 = new Set([
    {name: 'a'},
    {name: 'b', lastname: 'bb'},
    {name: 'c'},
    {name: 'd'}
]);

const set2 = new Set([
    {name: 'b'},
    {name: 'd'}
]);

set1.forEach((value) => {
    if (![...set2].some((o) => Object.entries(o).every(([k, v], _, arr) => (Object.keys(value).length === arr.length && value[k] === v)))) {
        set1.delete(value);
    }
})

console.log([...set1]);

What this does, is to iterate through set1 and if the item at the current iteration is not the same as any item in set2 (![...set2].some(..)), it is deleted. The items are considered the same if they have the same number of keys and if the values at the same key are strictly equal.

This only works if the values of the objects in the sets are primitives, if they are not, you'll have to change value[k] === v to an appropriate comparison.

Titus
  • 22,031
  • 1
  • 23
  • 33
0

One could write a generic solution which compares pure, thus JSON conform, data structures regardless of any object's nesting depth/level and (creation time) key order.

Such a function would be self recursive for Array item (order matters) and Object property (key order does not matter) comparison. Otherwise values are compared strictly.

function isDeepDataStructureEquality(a, b) {
  let isEqual = Object.is(a, b);

  if (!isEqual) {
    if (Array.isArray(a) && Array.isArray(b)) {

      isEqual = (a.length === b.length) && a.every(
        (item, idx) => isDeepDataStructureEquality(item, b[idx])
      );
    } else if (
      a && b
      && (typeof a === 'object')
      && (typeof b === 'object')
    ) {
      const aKeys = Object.keys(a);
      const bKeys = Object.keys(b);

      isEqual = (aKeys.length === bKeys.length) && aKeys.every(
        (key, idx) => isDeepDataStructureEquality(a[key], b[key])
      );
    }
  }
  return isEqual;
}

const objA = { // `objA` equals `objB`.
  name: 'foo',
  value: 1,
  obj: {
    z: 'z',
    y: 'y',
    a: {
      name: 'bar',
      value: 2,
      obj: {
        x: 'x',
        w: 'w',
        b: 'b',
      },
      arr: ['3', 4, 'W', 'X', {
        name: 'baz',
        value: 3,
        obj: {
          k: 'k',
          i: 'i',
          c: 'c',
        },
        arr: ['5', 6, 'B', 'A'],
      }],
    },
  },
  arr: ['Z', 'Y', 1, '2'],
};

const objB = { // `objB` equals `objA`.
  arr: ['Z', 'Y', 1, '2'],
  obj: {
    z: 'z',
    y: 'y',
    a: {
      obj: {
        x: 'x',
        w: 'w',
        b: 'b',
      },
      arr: ['3', 4, 'W', 'X', {
        obj: {
          k: 'k',
          i: 'i',
          c: 'c',
        },
        name: 'baz',
        value: 3,
        arr: ['5', 6, 'B', 'A'],
      }],
      name: 'bar',
      value: 2,
    },
  },
  name: 'foo',
  value: 1,
};

const objC = { // `objC` equals neither `objA` nor `objB`.
  arr: ['Z', 'Y', 1, '2'],
  obj: {
    z: 'z',
    y: 'y',
    a: {
      obj: {
        x: 'x',
        w: 'w',
        b: 'b',
      },
      arr: ['3', 4, 'W', 'X', {
        obj: {
          k: 'k',
          i: 'i',
          c: 'C', // the single difference to `objA` and `objB`.
        },
        name: 'baz',
        value: 3,
        arr: ['5', 6, 'B', 'A'],
      }],
      name: 'bar',
      value: 2,
    },
  },
  name: 'foo',
  value: 1,
};

console.log(
  'isDeepDataStructureEquality(objA, objB) ?..',
  isDeepDataStructureEquality(objA, objB)
);
console.log(
  'isDeepDataStructureEquality(objA, objC) ?..',
  isDeepDataStructureEquality(objA, objC)
);
console.log(
  'isDeepDataStructureEquality(objB, objC) ?..',
  isDeepDataStructureEquality(objB, objC)
);
console.log(
  'isDeepDataStructureEquality(objB, objA) ?..',
  isDeepDataStructureEquality(objB, objA)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Based on the above implementation of isDeepDataStructureEquality one can solve the OP's task, that actually looks for the intersection of two list structures, by additionally providing a getIntersectionOfDeeplyEqualDataStructures functionality ...

function getIntersectionOfDeeplyEqualDataStructures(a, b) {
  return [...(a ?? [])]
    .reduce((collector, sourceItem) => {

      const { target, intersection } = collector;

      const targetIndex = target.findIndex(targetItem =>
        isDeepDataStructureEquality(targetItem, sourceItem)
      );
      if (targetIndex >= 0) {
        // collect the intersection of
        // both, source (a) and target (b).

        intersection.push(target[targetIndex]);
      }
      return collector;

    }, {

      target: [...(b ?? [])],
      intersection: [],

    }).intersection;
}

const set1 = new Set([
  { name: 'a' },
  { name: 'b', lastname: 'bb' },
  { name: 'c' },
  { name: 'd' }
]);
const set2 = new Set([
  { name: 'b' },
  { name: 'd' },
]);
console.log(
  "getIntersectionOfDeeplyEqualDataStructures(set1, set2) ...",
  getIntersectionOfDeeplyEqualDataStructures(set1, set2)
);

const set3 = new Set([
  { name: 'a' },
  { name: 'b', lastname: 'bb' },
  { name: 'c' },
  {
    name: 'd',
    list: ['foo', 1, null, false, 0, {
      foo: { bar: { baz: 'bizz', buzz: '' } }
    }],
  },
]);
const set4 = new Set([
  {
    list: ['foo', 1, null, false, 0, {
      foo: { bar: { buzz: '', baz: 'bizz' } }
    }],
    name: 'd',
  },
  { name: 'C' },
  { lastname: 'bb', name: 'b' },
  { name: 'aa' }
]);
console.log(
  "getIntersectionOfDeeplyEqualDataStructures(set3, set4) ...",
  getIntersectionOfDeeplyEqualDataStructures(set3, set4)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
  function isDeepDataStructureEquality(a, b) {
    let isEqual = Object.is(a, b);

    if (!isEqual) {
      if (Array.isArray(a) && Array.isArray(b)) {

        isEqual = (a.length === b.length) && a.every(
          (item, idx) => isDeepDataStructureEquality(item, b[idx])
        );
      } else if (
        a && b
        && (typeof a === 'object')
        && (typeof b === 'object')
      ) {
        const aKeys = Object.keys(a);
        const bKeys = Object.keys(b);

        isEqual = (aKeys.length === bKeys.length) && aKeys.every(
          (key, idx) => isDeepDataStructureEquality(a[key], b[key])
        );
      }
    }
    return isEqual;
  }
</script>

Edit

As for Titus' approach ...

set1.forEach(value => {
  if (
    ![...set2].some(o =>
      Object.entries(o).every(([k, v], _, arr) =>
        (Object.keys(value).length === arr.length && value[k] === v)
      )
    )
  ) {
    set1.delete(value);
  }
});

... which works for flat objects only, though already agnostic to key insertion order, one could optimize the code by ...

  • ... not creating the keys array of the most outer currently processed object again and again with every nested some and every iteration.

    • thus, something like ... const valueKeys = Object.keys(value); ... before the if clause, already helps improving the code.
  • ... inverting the nested some and every logic which does result in a more efficient way of ... deleting every flat data-item from the processed set which does not equal any flat data-item from the secondary set.

On top of that, one could implement a function statement which not only helps code-reuse but also makes the implementation independent from outer scope references.

For instance, the primary set which is operated and going to be mutated can be accessed as such a function's third parameter. But most important for outer scope independency is the also available thisArg binding for any set's forEach method. Thus any function statement or function expression can access e.g. the other/secondary set via this in case the latter was passed as the forEach's 2nd parameter.

Also an improved wording supports a better readability of the code ...

//the function naming of cause is exaggerated.
function deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget(sourceItem, _, sourceSet) {
  const targetSet = this;

  const sourceKeys = Object.keys(sourceItem);
  if (
    // ... for any data-item from the (bound) target-set ...
    [...targetSet].every(targetItem =>

      // ... which does not equal the currently processed data-item from the source-set ...
      Object.entries(targetItem).some(([targetKey, targetValue], _, targetEntries) =>
        sourceKeys.length !== targetEntries.length || sourceItem[targetKey] !== targetValue
      )
    )
  ) {
    // ... delete the currently processed data-item from the source-set.
    sourceSet.delete(sourceItem);
  }
}

const set1 = new Set([
  { name: 'a' },                        // - to be kept.
  { name: 'b', lastname: 'bb' },        // - to be kept.
  { name: 'c' },                        // - to be deleted.
  { name: 'd', nested: { name: 'a' } }, // - to be kept, but fails ...
]);                                     //   ... due to not being flat.
const set2 = new Set([
  { name: 'd', nested: { name: 'a' } }, // - should equal, but doesn't.
  { name: 'a' },                        // - does equal.
  { lastname: 'bb', name: 'b' },        // - does equal.
  { name: 'e' },                        // - doesn't equal.
]);

// `set1` is going to be mutated.
set1.forEach(deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget, set2);

console.log(
  'mutated `set1` now (almost) being equal to the intersection of initial `set1` and `set2` ...',
  [...set1]
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
-1

const set1 = new Set([
    {name: 'a'},
    {name: 'b', lastname: 'bb'},
    {name: 'c'},
    {name: 'd'}
]);

const set2 = new Set([
    {name: 'b'},
    {name: 'd'}
]);

const names = [...set2].map(s2 => s2.name);

console.log([...set1].filter(item => names.includes(item.name)));

const set1 = new Set([
    {name: 'a'},
    {name: 'b', lastname: 'bb'},
    {name: 'c'},
    {name: 'd'},
    {name: 'e'}
]);

const set2 = new Set([
    {name: 'c', lastname: 'ccc'},
    {name: 'd'},
    {name: 'b', lastname: 'cc'},
    {name: 'e'}
]);

console.log([...set1].filter(item => {
    const s2Arr = [...set2];
    const itemKeys = Object.keys(item);
    for(let i = 0; i < s2Arr.length; i++){
        const s2Obj = s2Arr[i];
        const s2ObjKeys = Object.keys(s2Obj);
        if(s2ObjKeys.length == itemKeys.length){
            let oneSame = true;
            for(let j = 0; j < s2ObjKeys.length; j++){
                const s2ObjKey = s2ObjKeys[j];
                if(item[s2ObjKey] != s2Obj[s2ObjKey]){
                    oneSame = false;
                }
            }
            if(oneSame)
              return true;
        }
    }
    return false;
}));
Ian
  • 1,198
  • 1
  • 5
  • 15
  • As I said, I can't check based on key, because I have a lot of keys. It was just as example to show What I need. – Atousa Darabi Feb 07 '22 at 08:41
  • @AtousaDarabi Do you have to compare all the keys? If you clarify what you want and clarify the example data, it will help you answer. – Hyunjune Kim Feb 07 '22 at 08:43
  • @AtousaDarabi I'm a bit confused, can you provide additional input and output, or add more details of the problem, so that I might have a better idea of how to help you – Ian Feb 07 '22 at 08:45
  • I think if there be a way not to compare keys would be better. Because I have so many keys. – Atousa Darabi Feb 07 '22 at 08:55
  • @AtousaDarabi Do you mean to use all the properties of each group of objects in set2 as keys, I re-edited my answer, you can see if it is what you need – Ian Feb 07 '22 at 09:31
  • @AtousaDarabi My method is mainly distinguished by value, for me {name: 'b', lastname: 'b'} and {lastname: 'b', name: 'b'} are allowed, but if your condition It is more strict. If you need to compare the order of object properties, it is easier to handle. Just use JSON.stringify to convert the object into a string and compare the values. – Ian Feb 07 '22 at 09:50