Znalezienie unikalnych elementów tablicy lub usunięcie duplikatów to jedno z podstawowych czynności jakie wykonujemy jako programiści. Równie często takie zadanie pojawia się podczas rozmów rekrutacyjnych, ponieważ możliwości jego rozwiązania jest wiele, przynajmniej w JS. Sam kilka razy byłem o to pytany, a w zależności od odpowiedzi udowodnisz swoje doświadczenie i znajomość języka.
Oto przykład tablicy, która zawiera różne elementy, także duplikaty.
var items = ['a', 1, 'a', 2, '1'];
Nasz cel to tablica unikalnych wartości, czyli takich co występują tylko raz.
var unique;
console.log(unique); // ['a', 1, 2, '1']
Zwróć uwagę, że 1
oraz '1'
to mimo wszystko różne wartości, ponieważ jedna jest liczbą (integer), a druga tekstem (string).
Użycie obiektu {}
Pierwszy pomysł jaki przychodzi do naszej głowy to sprawdzenie wartości tablicy dzięki pętli i zapisanie ich do obiektu {}
. Atutem obiektu jest fakt, że właściwości nie mogą powtarzać się.
var obj = {};
items.forEach(function(val) {
obj[val + '::' + typeof val] = val;
});
unique = Object.keys(obj).map(function(val) {
return obj[val];
});
console.log(unique); // ['a', 1, 2, '1']
Kod wydaje się skomplikowany, ale jest całkiem prosty. Tworzymy zmienną obj
przez przypisanie do niej obiektu, używając nawiasów klamrowych. W dalszej części iterujemy po tablicy items
, gdzie każdą wartość z tablicy przypisujemy do obiektu obj
.
Zwróćmy uwagę na klucz jaki stosowany jest dla każdej wartości. Nie jest to tylko wartość, ale połączenie wartości oraz jej typu, rozdzielone dwoma dwukropkami. Dzięki temu 1
oraz '1'
zostały zapisane pod różnymi kluczami. Inaczej jedna wartość nadpisałaby poprzednią, gdyż klucze obiektu są zawsze tekstem.
Nasz zmienna obj
zawiera teraz tylko unikalne wartości, ale potrzebujemy ich w formie tablicy. W tym celu korzystamy z wbudowanej metody keys
dla typu Object
, który zwraca klucze, a te mapujemy do tablicy poprzez funkcję zwracającą wartość obiektu obj
. Tablica wynikowa została przypisana do zmiennej unique
.
Użycie tablicy []
Innym rozwiązaniem tego problemu jest wykorzystanie dodatkowej tablicy zamiast obiektu. Tutaj ponownie iterujemy po tablicy. Sposób przejrzenia tablicy nie ma większego znaczenia i może to być metoda forEach, pętla for z wykorzystaniem operatorów in albo of, bądź praktycznie dowolna inna pętla, która dostarczy nam wartość każdego elementu tablicy.
unique = [];
for (var itm in items) {
if (unique.indexOf(items[itm]) === -1) {
unique.push(items[itm]);
}
}
console.log(unique); // ['a', 1, 2, '1']
Główne założenia tego pomysłu to sprawdzenie czy wartość danego elementu tablicy bazowej istnieje w naszej dodatkowej tablicy? Jeśli nie to go dopisujemy do niej. W innym razie mamy do czynienia z duplikatem, ponieważ taki element istnieje już w tablicy dodatkowej.
Sprawdzamy to poprzez wywołanie metody indexOf
dla tablicy unique
z argumentem items[itm]
, czyli danego elementu iteracji i porównujemy z liczbą -1
. Taki wynik jest zwracany, kiedy szukana wartość nie występuje w tablicy, inaczej jest to indeks, pod którym znajdziemy wartość.
Po spełnieniu warunku przypisuje wartość items[itm]
do dodatkowej tablicy przez użycie metody push
. Dodatkowo możemy testować czy wartość jaką przypisujemy nie jest pusta lub cokolwiek innego, jeśli jest taka potrzeba.
Połączenie filter
oraz indexOf
Inne rozwiązanie gwarantujące unikalność elementów tablicy to wykorzystanie dwóch innych metod dla tablic. Działanie indexOf
znamy z poprzedniego przykładu. Pora na użycie metody filter
, która sprawdza wszystkie elementy tablicy i zwraca jedynie te spełniające warunki funkcji.
function uniqueValues(val, idx, arr) {
return arr.indexOf(val) === idx;
}
unique = items.filter(uniqueValues);
console.log(unique); // ['a', 1, 2, '1']
Stworzyliśmy tutaj dodatkową funkcję, ale jej ciało równie dobrze można przekazać jako argument metody filter. Wszystko zależy o nas i style jaki preferujemy. Czasami zagnieżdżanie definicji funkcji utrudnia czytelność kodu, ale do rzeczy.
Idea tego rozwiązania to testowanie czy indeks wartości aktualnie iterowanego elementu tablicy jest równy temu indeksowi. Brzmi skomplikowanie, ale podobnie jak poprzednio indexOf
zwraca indeks, pod którym dana wartość siedzi w tablicy. Definicja funkcji zwrotnej (ang. callback) użytej w metodzie filter
przyjmuje jako argumenty: wartość elementu tablicy, kolejno jego indeks oraz samą tablicę. Mając takie zmienne jesteśmy w stanie porównać czy indeks danej wartości w tablicy arr.indexOf(val)
jest indeksem przetwarzanego aktualnie elementu, czyli idx
. Jeśli tak nie jest to znaczy, że ta wartość jest duplikatem, bo metoda indexOf
zwraca miejsce pierwszego wystąpienia danej wartości w tablicy lub -1
w przypadku braku takiej wartości.
Nowość z ES6
Jeżeli dotychczasowe przykłady były skomplikowane to poniższy kod sprawi, że zaczniesz przecierać oczy ze zdziwienia. Nie od dziś wiadomo, że programiści to leniwi ludzie, dlatego nawet pisanie kodu ograniczają do minimum. Nowe możliwości oferowane przez ES6 to często skrótowy zapis znanych i działających wcześniej funkcjonalności. Kilka znaków mniej sprawia, że mamy więcej czasu na grę w piłkarzyki lub na konsoli, a kod jawi się jako tajemnicze zaklęcia znane tylko doświadczonym programistom.
unique = [...new Set(items)];
console.log(unique); // ['a', 1, 2, '1']
Wyjaśnienie tego „jedno-linijkowca” jest równie proste jak jego zapis. Należy tylko zapamiętać, że Set
to nowy typ dostępny w ES6, który umożliwia przechowywanie unikalnych wartości, a tego właśnie potrzebujemy. Dodatkowo jako argument przekazujemy coś „iterowalnego”, czyli naszą tablicę wartości, która zawiera wspomniane duplikaty. Zbiór z definicji zadba, aby te nie powtarzały się.
Teraz pozostaje przekształcić te unikalne wartości do klasycznej tablicy, co umożliwia operator spread
. Jego działanie jest bardzo proste, ponieważ „rozwija” elementy, które można do listy wartości rozdzielonych przecinkiem, a te można przekazać jako argument funkcji lub właściwie elementy tablicy.
Array.from(new Set(items));
Powyższy zapis jest w sumie równoważny temu z ostatniego przykładu, i może być czytelniejszy dla wielu osób, ale tamten jest krótszy i warto zapamiętać jak w praktyce działa, bo na wielu osobach, szczególnie mniej technicznych zrobi duże wrażenie.
Podsumowanie
Istnieje pewnie co najmniej drugie tyle sposobów usunięcia duplikatów z tablicy. Jedne są bardziej, a drugie mniej wydajne. Czytelność także ma znaczenie, bo przecież nikt nie chce kodu, którego nie rozumie, a jedynie skopiował ze Stackoverflow. Tak jak wiele dróg prowadzi do jednego miejsca i wybieramy te, które są wygodne i przyjazne, tak wiele rozwiązań daje ten sam efekt, ale różnym kosztem. Im wcześniej to zrozumiemy tym lepiej dla nas i naszego kodu.
Jeśli znacie inne, ciekawe sposoby na pozbycie się duplikatów z tablicy to napiszcie w komentarzach.