0

I have an array of objects

[
{
    id1: {
        props1: 1,
        props2: 2,
        props3: 3
    }
},
{
    id2: {
        props1: 1,
        props2: 3,
        props3: 4
    }
},
{
    id3: {
        props1: 1,
        props2: 2,
        props3: 4
    }
},
{
    id4: {
        props1: 2,
        props2: 2,
        props3: 3
    }
},
{
    id5: {
        props1: 2,
        props2: 2,
        props3: 4
    }
}]

I want to compare elements (objects) each other to get all couple of objects containing same props1 and props2

So my result should be

[
    [
        {
            id1: {
                props1: 1,
                props2: 2,
                props3: 3
            }
        },
        {
            id3: {
                props1: 1,
                props2: 2,
                props3: 4
            }
        }
    ],
    [
        {
            id4: {
                props1: 2,
                props2: 2,
                props3: 3
            }
        },
        {
            id5: {
                props1: 2,
                props2: 2,
                props3: 4
            }
        }
    ]
]

Is there any way to compare 2 elements (objects) each other without using 2 for-loop? I'm worried about the performance of 2 for-loop solution when the size of array is a big number

Vu Le Anh
  • 708
  • 2
  • 8
  • 21
  • what did you try? – Hien Nguyen Jun 24 '19 at 11:02
  • 1
    You can probably keep a map of all results or something. I'm not really sure what the best approach would be though - it's hard to generalise from this example. What happens if there are more than one set of items that have the same `props1` and `props2`? – VLAZ Jun 24 '19 at 11:03
  • 1
    You don't have an "array of objects" You have an array that contains a single object and some funky formatting. Please update your question to clarify. – spender Jun 24 '19 at 11:05
  • 1
    Please take your time to re-edit your question so that it doesn't contain errors. Your most recent edit now presents uncompilable code. – spender Jun 24 '19 at 11:09
  • I really want to help you, but you keep on presenting broken code. Once again, your edit doesn't compile. Arrays do not have string keys. `[id1:{}]` simply won't compile. – spender Jun 24 '19 at 11:21
  • You will either have to write in the format of `{id1: {}}` or `[{}]`. The format `[id1: {}]` is not valid in javascript. – nick zoum Jun 24 '19 at 11:37

2 Answers2

0

You could group the list and then get the groups with at least 2 elements.

Object.defineProperty(Array.prototype, "groupBy", {
  configurable: false,
  writable: false,
  value: function(expression) {
    return Array.prototype.reduce.call(this, function(result, value, index, array) {
      var key = expression.call(array, value, index, array);
      if (!(key in result)) result[key] = [];
      result[key].push(value);
      return result;
    }, {});
  }
});

var groups = [{
    props1: 1,
    props2: 2,
    props3: 3
  },
  {
    props1: 1,
    props2: 3,
    props3: 4
  },
  {
    props1: 1,
    props2: 2,
    props3: 4
  },
  {
    props1: 2,
    props2: 2,
    props3: 3
  }
].groupBy(x => `${x.props1}, ${x.props2}`);

console.log("All the groups: ");
console.log(groups);

console.log("Groups with at least 2 items: ");
console.log(Object.keys(groups).filter(key => groups[key].length > 1).map(key => groups[key]));

If you absolutely want to only do 1 pass you could do it the following way by sacrificing abstraction.

function groupArray(list, expression) {
  var tempList = {};
  return list.reduce(function(result, value, index, array) {
    var key = expression.call(array, value, index, array);
    if (!(key in tempList)) tempList[key] = [];
    else result[key] = tempList[key];
    tempList[key].push(value);
    return result;
  }, {});
}

var groups = groupArray([{
    props1: 1,
    props2: 2,
    props3: 3
  },
  {
    props1: 1,
    props2: 3,
    props3: 4
  },
  {
    props1: 1,
    props2: 2,
    props3: 4
  },
  {
    props1: 2,
    props2: 2,
    props3: 3
  }
], x => `${x.props1}, ${x.props2}`);

console.log("All the groups with at least 2 elements: ");
console.log(groups);
nick zoum
  • 7,216
  • 7
  • 36
  • 80
  • ...and monkeypatching native objects is considered harmful. See https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice – spender Jun 24 '19 at 11:29
  • @spender Second example should probably be in plain function format. But the first one is a decent addition to the `Array` class. – nick zoum Jun 24 '19 at 12:15
  • sorry for the mistake in my question. But, I don't refer the way to add method to Object of Javascript – Vu Le Anh Jun 24 '19 at 14:10
  • @VuLeAnh Did you see the full answer? The second snippet should do what you are asking for. I just also added a more generic approach at the top. – nick zoum Jun 24 '19 at 15:15
0

Shameless self-plug: My library, blinq is very handy for efficiently performing this kind of transformation.

const {
  blinq,
  deepComparer
} = window.blinq;
const data = [{
    id1: {
      props1: 1,
      props2: 2,
      props3: 3
    }
  },
  {
    id2: {
      props1: 1,
      props2: 3,
      props3: 4
    }
  },
  {
    id3: {
      props1: 1,
      props2: 2,
      props3: 4
    }
  },
  {
    id4: {
      props1: 2,
      props2: 2,
      props3: 3
    }
  },
  {
    id5: {
      props1: 2,
      props2: 2,
      props3: 4
    }
  }
]

const transformedData = blinq(data)
  .selectMany(x => Object.entries(x))
  .groupBy(([k, v]) => ({
    props1: v.props1,
    props2: v.props2
  }), deepComparer)
  .where(g => g.count() > 1)
  .select(g => g
    .select(x => Object.fromEntries([x]))
    .toArray()
  )
  .toArray();

console.log(transformedData);
<script src="https://cdn.jsdelivr.net/npm/blinq"></script>
spender
  • 117,338
  • 33
  • 229
  • 351
  • Thanks spender. I will consider to use your library later. It's interesting. Currently, I want to implement the solution by myself. Do you have any suggestion for me, for example transform to a data type which is efficient traversing data? – Vu Le Anh Jun 24 '19 at 14:48