Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get distinct values from an array of arrays in JavaScript using the filter() method? [duplicate]

I have an array like this:

let x = [[1, 2], [3, 4], [1, 2], [2, 1]];

What should I do to retrieve an array without the duplicates?

[[1, 2], [3, 4], [2, 1]];

I would like to use the filter method. I tried this but it doesn't work:

x.filter((value,index,self) => (self.indexOf(value) === index))

EDIT: as I specified to use the filter method, I don't think this question is a duplicate. Also, I got several interesting answers.

like image 861
Snorlite Avatar asked Aug 19 '19 19:08

Snorlite


4 Answers

Try converting the inner arrays to a string, then filter the dupes and parse the string again.

let x = [[1, 2], [3, 4], [1, 2]];

var unique = x.map(ar=>JSON.stringify(ar))
  .filter((itm, idx, arr) => arr.indexOf(itm) === idx)
  .map(str=>JSON.parse(str));

console.log(unique);
like image 152
I wrestled a bear once. Avatar answered Nov 15 '22 07:11

I wrestled a bear once.


Filter just causes things to get into O(n^2).

The currently accepted answer uses .filter((itm, idx, arr) => arr.indexOf(itm) === idx) which will cause the array to be iterated each time during each iteration... n^2.

Why even go there? Not only that, you need to parse in the end. It is a lot of excess.

There is no real good way to use filter without hitting O(n^2) here, so if performance is the goal is should probably be avoided.


Instead, just use reduce. It is very straightforward and fast easily accomplishing O(n).

"Bin reduce the set to unique values."

let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
let y = Object.values(x.reduce((p,c) => (p[JSON.stringify(c)] = c,p),{}));
console.log(y);

In case it isn't as clear, here is a more readable version of the bin reduction.

// Sample Data
let dataset = [[1, 2], [3, 4], [1, 2], [2, 1]];

// Create a set of bins by iterating the dataset, which
// is an array of arrays, and structure the bins as
//     key: stringified version of the array
//     value: actual array
let bins = {};

// Iteration
for(let index = 0; index < dataset.length; index++){
 // The current array, from the array of arrays
 let currentArray = dataset[index];
 
 // The JSON stringified version of the current array
 let stringified = JSON.stringify(currentArray);
 
 // Use the stringified version of the array as the key in the bin,
 // and set that key's value as the current array
 bins[stringified] = currentArray;
}

// Since the bin keys will be unique, so will their associated values. 
// Discard the stringified keys, and only take the set of arrays to
// get the resulting unique set.
let results = Object.values(bins);

console.log(results);

If you were to have to go the route of filter, then n^2 must be used. You can iterate each item looking for existence using every.

"Keep every element which does not have a previous duplicate."

let x = [
  [1, 2],
  [3, 4],
  [1, 2],
  [2, 1]
];
let y = x.filter((lx, li) =>
  x.every((rx, ri) =>
    rx == lx ||
    (JSON.stringify(lx) != JSON.stringify(rx) || li < ri))
);
console.log(y);
like image 37
Travis J Avatar answered Nov 15 '22 07:11

Travis J


Okay, the string hash idea is brilliant. Props to I wrestled a bear once. I think the code itself could be a bit better though, so here's how I tend to do this type of thing:

let x = [[1, 2], [3, 4], [1, 2]];
const map = new Map();
x.forEach((item) => map.set(item.join(), item));
console.log(Array.from(map.values()));

And if you want an ugly one liner:

let x = [[1, 2], [3, 4], [1, 2]];
const noRepeats = Array.from((new Map(x.map((item) => [item.join(), item]))).values());
console.log(noRepeats);
like image 41
Seph Reed Avatar answered Nov 15 '22 08:11

Seph Reed


The equivalent to

x.filter((value,index,self) => (self.indexOf(value) === index))

would be

x.filter((v,i,self) => {
for1:
  for (let j = 0; j < self.length; j++) {
    if (i == j) {
      return true;
    }
    if (self[j].length != v.length) {
      continue;
    }
    for (let k = 0; k < v.length; k++) {
      if (self[j][k] != v[k]) {
        continue for1;
      }
    }
    return false;
  }
  return true;
})

Unlike some of the other answers, this does not require a conversion to string and can thus work with more complex values. Use === instead of == if you want.

The time complexity is not great, of course.

like image 38
Andrea Avatar answered Nov 15 '22 06:11

Andrea