3

so I have an array containing some objects. I want to compare these objects with each other. If the fromId AND the toId properties are the same, it is an duplicate.

Example:

const data = [{
  fromId: 1,
  toId: 2,
  finished: true
}, {
  fromId: 1,
  toId: 2,
  finished: false
}, {
  fromId: 5,
  toId: 9,
  finished: false
}, {
  fromId: 1,
  toId: 5,
  finished: true
}, {
  fromId: 2,
  toId: 1,
  finished: false
}];

$(document).ready(() => {
  const duplicates = data.filter(x =>
    data
    .filter(y => y.fromId == x.fromId && y.toId == x.toId)
    .length > 1
  );

  console.log(duplicates);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

And I want to get the length of this data array but only with unique values. I tried to remove all duplicate values and found a solution here

Get all non-unique values (i.e.: duplicate/more than one occurrence) in an array

const data = [1, 2, 3, 4, 5, 2, 2, 6, 7, 8, 2, 9, 2, 2, 2, 2, 2];

$(document).ready(() => {
  const uniqueValues = data.filter((connection, index) => data.indexOf(connection) == index);

  console.log(uniqueValues);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

but on this solution I just use indexOf for the whole object. How can I compare the fromId and toId and remove the duplicate on same matches?

From the original array I would remove one

{
      fromId: 1,
      toId: 2,
      finished: false
    }

because the fromId and toId exist already at another object.

6 Answers6

1

Rather than filter nested in another filter (bad runtime complexity), since you're just looking for the length of the resulting unique array, you might instead combine each fromId and toId into a string, put that string into a Set (which only retains unique values), and then check that Set's size:

const data=[{fromId:1,toId:2,finished:!0},{fromId:1,toId:2,finished:!1},{fromId:5,toId:9,finished:!1},{fromId:1,toId:5,finished:!0},{fromId:2,toId:1,finished:!1}];

const idSet = new Set(data.map(({ fromId, toId }) => fromId + '_' + toId));
console.log(idSet.size);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
1

You can use Array.reduce() to filter the array with unique objects and then get the length value together with the unique array:

const data = [{
  fromId: 1,
  toId: 2,
  finished: true
}, {
  fromId: 1,
  toId: 2,
  finished: false
}, {
  fromId: 5,
  toId: 9,
  finished: false
}, {
  fromId: 1,
  toId: 5,
  finished: true
}, {
  fromId: 2,
  toId: 1,
  finished: false
}];

$(document).ready(() => {
  var uniqueArray = data.reduce((acc, obj)=>{
    var existObj = acc.find(item => item.fromId === obj.fromId && item.toId === obj.toId);
    if(existObj){
      return acc;
    }
    acc.push(obj);
    return acc;
  },[]);
console.log('unique array ', uniqueArray);
console.log('length of unique object ', uniqueArray.length);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Ankit Agarwal
  • 30,378
  • 5
  • 37
  • 62
0

You can create a new array, and push the lements only once, checking if they already exists or not using inArray function from jQuery:

const data = [{
  fromId: 1,
  toId: 2,
  finished: true
}, {
  fromId: 1,
  toId: 2,
  finished: false
}, {
  fromId: 5,
  toId: 9,
  finished: false
}, {
  fromId: 1,
  toId: 5,
  finished: true
}, {
  fromId: 2,
  toId: 1,
  finished: false
}];

$(document).ready(() => {
   
  var filtered = [];
  $.each(data, function(i, item){
      if($.inArray(item, filtered) === -1){
        filtered.push(item);
      }
      
      
  });
  console.log(filtered.length);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

jQuery inArray()

Damian Peralta
  • 1,846
  • 7
  • 11
0

While CertainPerformance's answer is probably better for your scenario, the indexOf alternative that does not compare item identities would be findIndex. However, this is ES6+ only, but most modern browsers implement it (sorry, no IE11).

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

Capricorn
  • 2,061
  • 5
  • 24
  • 31
0

Have a go with a simple forEach - this is plain JS and not ES5/6 or jQuery

const data = [{fromId: 1,toId: 2,finished: true},{fromId: 1,toId: 2,finished: false}, {fromId: 5,toId: 9,finished: false}, {fromId: 1,toId: 5,finished: true}, {fromId: 2, toId: 1, finished: false }];
var combos = [];
var  dupes = [];
var unique = [];

data.forEach(function(item){
  var combo = ""+item.fromId+"."+item.toId;
  if (combos.indexOf(combo) == -1) {
    combos.push(combo);
    unique.push(item);
  }
  else {
    dupes.push(item);
  }
});
console.log("dupes",dupes);
console.log("unique",unique,unique.length);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
0

Yeah, many options available... Will give you another one with lodash/fp:

Assuming you have:

import {flow, map, uniq} from 'lodash/fp'

Do this:

const count = flow(
    map(o => o.fromId + '.' + o.toId),
    uniq,
  )(data).length

console.log('Count: ', count)
stas.yaranov
  • 1,797
  • 10
  • 17