Home > Back-end >  Filter one combination from 6 combinations in an array of objects
Filter one combination from 6 combinations in an array of objects

Time:11-04

So I have this array of objects which consists of items where one item have 6 combinations which are posed as objects.

Suppose I have one combination of this object:

{ "1": "ab", "2": "bc", "3": "ac" }

The array also consists of 5 other objects with different combinations of that object. Like this:

{ "1": "ab", "2": "ac", "3": "bc" }
{ "1": "ac", "2": "bc", "3": "ab" }
{ "1": "ac", "2": "ab", "3": "bc" }
{ "1": "bc", "2": "ac", "3": "ab" }
{ "1": "bc", "2": "ab", "3": "ac" }

This way I have around 1000 items with 6 combinations of each of them in an array and I need to filter and return only 1 combination of each item.

So suppose this is an array of 12 different combinations of two items:

[
    { "1": "ab", "2": "bc", "3": "ac" }
    { "1": "ab", "2": "ac", "3": "bc" }
    { "1": "ac", "2": "bc", "3": "ab" }
    { "1": "ac", "2": "ab", "3": "bc" }
    { "1": "bc", "2": "ac", "3": "ab" }
    { "1": "bc", "2": "ab", "3": "ac" }

    { "1": "de", "2": "ef", "3": "df" }
    { "1": "df", "2": "ef", "3": "de" }
    { "1": "de", "2": "df", "3": "ef" }
    { "1": "ef", "2": "df", "3": "de" }
    { "1": "df", "2": "de", "3": "ef" }
    { "1": "ef", "2": "de", "3": "df" }
]

I want to filter that array and return only 1 combination of each, like so:

[
    { "1": "ab", "2": "bc", "3": "ac" }
    { "1": "de", "2": "ef", "3": "df" }
]

Please note that "ab" in first object can be a value in whole different combination like:

{ "1": "ab", "2": "df", "3": "fe" }

How can I do this efficiently and with good performance in mind?

CodePudding user response:

If the values are always strings you can create a composite key from the sorted Object.values() of each element and then use a simple 'group by' operation, here using reduce() accumulating into a Map.

I'm only setting on the first match here (only if the map doesn't have a matching entry) which will return the first match, if you want the last match, just set the key on every iteration.

const input = [
  { 1: 'ab', 2: 'bc', 3: 'ac' },
  { 1: 'ab', 2: 'ac', 3: 'bc' },
  { 1: 'ac', 2: 'bc', 3: 'ab' },
  { 1: 'ac', 2: 'ab', 3: 'bc' },
  { 1: 'bc', 2: 'ac', 3: 'ab' },
  { 1: 'bc', 2: 'ab', 3: 'ac' },

  { 1: 'de', 2: 'ef', 3: 'df' },
  { 1: 'df', 2: 'ef', 3: 'de' },
  { 1: 'de', 2: 'df', 3: 'ef' },
  { 1: 'ef', 2: 'df', 3: 'de' },
  { 1: 'df', 2: 'de', 3: 'ef' },
  { 1: 'ef', 2: 'de', 3: 'df' },
];

const result = [
  ...input
    .reduce((a, o) => {
      const key = Object.values(o)
        .sort((a, b) => a.localeCompare(b))
        .join('_');

      if (!a.has(key)) a.set(key, o);

      return a;
    }, new Map())
    .values(),
];

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

If you don't mind returning the last matched combination you can simplify this into a map() within a new Map() call.

const input = [{ 1: 'ab', 2: 'bc', 3: 'ac' }, { 1: 'ab', 2: 'ac', 3: 'bc' }, { 1: 'ac', 2: 'bc', 3: 'ab' }, { 1: 'ac', 2: 'ab', 3: 'bc' }, { 1: 'bc', 2: 'ac', 3: 'ab' }, { 1: 'bc', 2: 'ab', 3: 'ac' }, { 1: 'de', 2: 'ef', 3: 'df' }, { 1: 'df', 2: 'ef', 3: 'de' }, { 1: 'de', 2: 'df', 3: 'ef' }, { 1: 'ef', 2: 'df', 3: 'de' }, { 1: 'df', 2: 'de', 3: 'ef' }, { 1: 'ef', 2: 'de', 3: 'df' },];

const result = [
  ...new Map(
    input.map((o) => [
      Object.values(o)
        .sort((a, b) => a.localeCompare(b))
        .join('_'),
      o,
    ])
  ).values(),
];

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Here's a quick example of how you might sort the result array returned by the filter. It's just a function that accepts a compare function, sorts the Object.values of the passed combination object and then rebuilds the object from the sorted array of values (here using Object.fromEntries(), but if you don't mind 0 indexed objects you could use Object.assign() and forego the extra map(), ie. return Object.assign({}, Object.values(obj).sort(compare));)

const input = [
  { 1: 'ab', 2: 'bc', 3: 'ac' },
  { 1: 'ab', 2: 'ac', 3: 'bc' },
  { 1: 'ac', 2: 'bc', 3: 'ab' },
  { 1: 'ac', 2: 'ab', 3: 'bc' },
  { 1: 'bc', 2: 'ac', 3: 'ab' },
  { 1: 'bc', 2: 'ab', 3: 'ac' },

  { 1: 'de', 2: 'ef', 3: 'dc' },
  { 1: 'dc', 2: 'ef', 3: 'de' },
  { 1: 'de', 2: 'dc', 3: 'ef' },
  { 1: 'ef', 2: 'dc', 3: 'de' },
  { 1: 'dc', 2: 'de', 3: 'ef' },
  { 1: 'ef', 2: 'de', 3: 'dc' },
];

const result = [...new Map(input.map((o) => [Object.values(o).sort((a, b) => a.localeCompare(b)).join('_'), o,])).values(),];
console.log('Initial result: ', result);

function sort_combination(obj, compare = (a, b) => a.localeCompare(b)) {
  return Object.fromEntries(
    Object.values(obj)
      .sort(compare)
      .map((v, i) => [i   1, v])
  );
}

const includes_c = (a, b) => b.includes('c') - a.includes('c') || a.localeCompare(b);

const sorted = result.map((o) => sort_combination(o, includes_c));

console.log('Sorted result: ', sorted);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related