How to get distinct values from an array of arrays in JavaScript using the filter() method?ybmiaxYySتی1monbeٙp
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))
-
stackoverflow.com/questions/1960473/… – Peter 9 hours ago
-
Oof. This is a hard one. I can think of a few different ways to force it, but nothing eloquent. First heavy handed idea, is to not use filter and instead use a for loop. – Seph Reed 9 hours ago
-
Hey guys, please read this users question before calling it a duplicate. They tried the things you've linked to. Ultimately, this question is a bit difficult to write the code for in an eloquent way. – Seph Reed 9 hours ago
-
@SephReed How about a Set combined with a hashing function? – Alireza 9 hours ago
-
1@Iwrestledabearonce.no. Fixed question – Snorlite 9 hours ago
7 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);
-
Thanks man, perfect. – Snorlite 9 hours ago
-
Much simpler:
Array.from(new Set(arr.map(x=>JSON.stringify(x))), x=>JSON.parse(x))
– Bergi 22 mins ago
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);
This is a solution with time complexity of O(n) where n is the number of elements in your array.
Using the filter method as the OP wants it:
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = x.filter(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
return true;
}
return false
})
console.log(res)
My personal preference here is to use ForEach as it looks more readable.
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = [];
x.forEach(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
res.push(el)
}
})
console.log(res);
We are using a Set and a simple combination of the elements of the array to make sure they are unique. Otherwise this would become O(n^2).
-
1@DominikMatis Added a solution with
filter
. – Alireza 8 hours ago -
what is the inner array is more than two elements? – I wrestled a bear once. 8 hours ago
-
if it's dynamic, we have to add another level of loop but this is still faster than a nested loop that works on the whole array twice. This would become O(n*m) where n === arr.length and m===arr[0].length, assuming that they are of the same length. – Alireza 8 hours ago
-
1it doesn't require another loop. consider changing
el[0]+""+el[1]
toel.join("")
seearray.join
. – I wrestled a bear once. 8 hours ago -
NOICE! Thanks bearman :D – Alireza 8 hours ago
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.
-
solid answer, +1, but it doesn't work if the array contains objects whereas the json approaches do. also,
self
is reserved in javascript, ideally that should have a different name. – I wrestled a bear once. 8 hours ago -
So you are saying it is not equivalent, because your version actually works? – Bergi 19 mins ago
-
@Iwrestledabearonce.
self
is totally fine as a local variable name. – Bergi 19 mins ago -
Why use these overcomplicated loops instead of simply exchanging
self.indexOf(value)
forself.findIndex(x => x[0]==value[0] && x[1]==value[1])
(or even usingevery
inside there if you want to support arbitrary-length arrays)? – Bergi 17 mins ago
Effective solution:
O(n)
operations, where n
is the length of x
array
O(n)
memory space, where n
is the length of x
array
const x = [[1, 2], [3, 4], [1, 2]];
const arrayTable = Object.create(null);
const uniqueArrays = x.filter(arr => {
const arrStr = JSON.stringify(arr);
if (!arrayTable[arrStr]) {
arrayTable[arrStr] = true;
return true;
}
return false;
});
console.log(uniqueArrays);
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 filter without hitting O(n^2) here.
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);
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);
-
Thank you, could you please explain me the logic behind the use of reduce? I don't understand the algorithm – Snorlite 4 hours ago
indexOf
does not work on identical instances of arrays
/objects
type elements within an array, as such arrays just hold references.
In filter function instance you get via parameter v
(in below code) is not the same instance as stored in array, making indexOf
unable to return the index of it.
In below code, by converting objects to strings we can use indexOf
to find duplicates.
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
console.log(x.
map(function(v){
return JSON.stringify(v)
})
.filter(function(v, i, o) {
return o.length == i ? true : o.slice(i + 1).indexOf(v) == -1;
})
.map(function(v) {
return JSON.parse(v)
})
);
-
indexof works perfectly fine on arrays. jsbin.com/qerubadoqo/edit?js,console – I wrestled a bear once. 8 hours ago
-
not if its an array of arrays, you cant find index of an array within an array using indexOf – Akash Shrivastava 8 hours ago
-
if you took 15 seconds to click on the link i gave you would see that that statement is 100% incorrect. indexof works perfectly fine with arrays. – I wrestled a bear once. 8 hours ago
-
you passed a reference in indexOf, not an array, try replacing 'a' with [2, 3] and see if it works – Akash Shrivastava 8 hours ago
-
1i removed my vote but your explanation is still not accurate. indexOf does "work" on arrays, but it doens't search for identical arrays, it's searches for the exact instance of the array you tell it to search for. you're on the right track using the word "reference" though. – I wrestled a bear once. 8 hours ago