Mam bardzo prostą tablicę JavaScript, która może, ale nie musi zawierać duplikatów.
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
Muszę usunąć duplikaty i umieścić unikalne wartości w nowej tablicy.
Mógłbym wskazać wszystkie kody, które I've próbował, ale myślę, że to's bezużyteczne, ponieważ oni nie'działają. Akceptuję również rozwiązania jQuery.
Używając konstruktora Set i składni spread syntax:
uniq = [...new Set(array)];
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
Zasadniczo iterujemy nad tablicą i dla każdego elementu sprawdzamy, czy pierwsza pozycja tego elementu w tablicy jest równa bieżącej pozycji. Oczywiście, te dwie pozycje są różne dla zduplikowanych elementów. Przy pomocy trzeciego ("ta tablica") parametru wywołania zwrotnego filtra możemy uniknąć zamknięcia zmiennej tablicowej:
uniqueArray = a.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
})
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
Tak to się zwykle robi. Chodzi o to, aby umieścić każdy element w tablicy hashtable, a następnie natychmiast sprawdzić jego obecność. To daje nam czas liniowy, ale ma co najmniej dwie wady:
uniq([1,"1"])
zwróci po prostu [1]
.uniq([{foo:1},{foo:2}])
zwróci tylko [{foo:1}]
.
To powiedziawszy, jeśli twoje tablice zawierają tylko prymitywy i nie dbasz o typy (np. to'zawsze liczby), to rozwiązanie jest optymalne.
To, co najlepsze z dwóch światówUniwersalne rozwiązanie łączy oba podejścia: używa hash lookups dla prymitywów i wyszukiwania liniowego dla obiektów.
function uniq(a) {
var prims = {"boolean":{}, "number":{}, "string":{}}, objs = [];
return a.filter(function(item) {
var type = typeof item;
if(type in prims)
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
else
return objs.indexOf(item) >= 0 ? false : objs.push(item);
});
}
Inną opcją jest posortowanie tablicy jako pierwszej, a następnie usunięcie każdego elementu równego poprzedniemu:
function uniq(a) {
return a.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
})
}
sort
). Dodatkowo, po cichu zmieniamy oryginalną tablicę jako efekt uboczny - nie jest to dobre! Jednakże, jeśli twoje dane wejściowe są już posortowane, to jest to droga do zrobienia (po prostu usuń sort
z powyższego).
Unikalne przez...Czasami pożądane jest, aby zunifikować listę na podstawie innych kryteriów niż tylko równość, na przykład, aby odfiltrować obiekty, które są różne, ale mają wspólną właściwość. Można to zrobić w elegancki sposób, przekazując wywołanie zwrotne. Ten "klucz" jest stosowany do każdego elementu, a elementy z równymi "kluczami" są usuwane. Ponieważ oczekuje się, że key
zwróci prymityw, tablica hash będzie działać dobrze tutaj:
function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
})
}
Szczególnie użytecznym key()
jest JSON.stringify
, który usunie obiekty, które są fizycznie różne, ale "wyglądają" tak samo:
a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]
Jeśli klucz
nie jest prymitywny, trzeba uciec się do wyszukiwania liniowego:
function uniqBy(a, key) {
var index = [];
return a.filter(function (item) {
var k = key(item);
return index.indexOf(k) >= 0 ? false : index.push(k);
});
}
W ES6 możesz użyć Set
:
function uniqBy(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
lub Map
:
function uniqBy(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
Podczas usuwania obiektów według klucza, możesz chcieć zachować pierwszy z "równych" obiektów lub ostatni.
Użyj powyższego wariantu Set
aby zachować pierwszy, oraz Map
aby zachować ostatni:
function uniqByKeepFirst(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
function uniqByKeepLast(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
//
data = [
{a:1, u:1},
{a:2, u:2},
{a:3, u:3},
{a:4, u:1},
{a:5, u:2},
{a:6, u:3},
];
console.log(uniqByKeepFirst(data, it => it.u))
console.log(uniqByKeepLast(data, it => it.u))
Zarówno underscore jak i Lo-Dash dostarczają metod uniq
. Ich algorytmy są w zasadzie podobne do pierwszego powyższego snippetu i sprowadzają się do tego:
var result = [];
a.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
});
To jest quadratic, ale są tam miłe dodatkowe bajery, jak owijanie natywnego indexOf
, możliwość uniqify przez klucz (iteratee
w ich języku), i optymalizacje dla już posortowanych tablic.
Jeśli używasz jQuery i nie możesz znieść niczego bez dolara przed nim, to idzie to tak:
$.uniqArray = function(a) {
return $.grep(a, function(item, pos) {
return $.inArray(item, a) === pos;
});
}
Wywołania funkcji są drogie w JavaScript, dlatego powyższe rozwiązania, tak zwięzłe jak są, nie są szczególnie wydajne. Aby uzyskać maksymalną wydajność, zastąp filter
pętlą i pozbądź się innych wywołań funkcji:
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
Ten kawałek brzydkiego kodu robi to samo, co snippet #3 powyżej, ale o rząd wielkości szybciej (od 2017 roku jest'tylko dwa razy szybszy - ludzie z JS core robią świetną robotę!)
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
/////
var r = [0,1,2,3,4,5,6,7,8,9],
a = [],
LEN = 1000,
LOOPS = 1000;
while(LEN--)
a = a.concat(r);
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq(a);
document.write('<br>uniq, ms/loop: ' + (new Date() - d)/LOOPS)
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq_fast(a);
document.write('<br>uniq_fast, ms/loop: ' + (new Date() - d)/LOOPS)
ES6 dostarcza obiekt Set, który znacznie ułatwia sprawę:
function uniq(a) {
return Array.from(new Set(a));
}
lub
let uniq = a => [...new Set(a)];
Na tej samej zasadzie można zbudować "leniwą", opartą na generatorach wersję uniq
:
function* uniqIter(a) {
let seen = new Set();
for (let x of a) {
if (!seen.has(x)) {
seen.add(x);
yield x;
}
}
}
// example:
function* randomsBelow(limit) {
while (1)
yield Math.floor(Math.random() * limit);
}
// note that randomsBelow is endless
count = 20;
limit = 30;
for (let r of uniqIter(randomsBelow(limit))) {
console.log(r);
if (--count === 0)
break
}
// exercise for the reader: what happens if we set `limit` less than `count` and why
Szybkie i brudne użycie jQuery:
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});
Vanilla JS: Usuń duplikaty używając obiektu jak zbioru.
Zawsze możesz spróbować umieścić go w obiekcie, a następnie iterować po jego kluczach:
function remove_duplicates(arr) {
var obj = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
obj[arr[i]] = true;
}
for (var key in obj) {
ret_arr.push(key);
}
return ret_arr;
}
Vanilla JS: Usuń duplikaty poprzez śledzenie już widzianych wartości (order-safe).
Lub, dla wersji bezpiecznej dla porządku, użyj obiektu do przechowywania wszystkich wcześniej widzianych wartości i sprawdź wartości z nim przed dodaniem do tablicy.
function remove_duplicates_safe(arr) {
var seen = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
if (!(arr[i] in seen)) {
ret_arr.push(arr[i]);
seen[arr[i]] = true;
}
}
return ret_arr;
}
ECMAScript 6: Użyj nowej struktury danych Set (order-safe).
ECMAScript 6 dodaje nową strukturę danych Set
, która pozwala na przechowywanie wartości dowolnego typu. Struktura Set.values
zwraca elementy w kolejności wstawiania.
function remove_duplicates_es6(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}
Przykładowe użycie:
a = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
b = remove_duplicates(a);
// b:
// ["Adam", "Carl", "Jenny", "Matt", "Mike", "Nancy"]
c = remove_duplicates_safe(a);
// c:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]
d = remove_duplicates_es6(a);
// d:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]