Obiekt `Set`

Ten artykuł wyjaśnia obiekt Set.

Wyjaśnimy obiekt Set na praktycznych przykładach.

YouTube Video

Obiekt Set

Set to wbudowany obiekt służący do obsługi kolekcji unikalnych, niepowtarzających się wartości. Pozwala uprościć eliminowanie duplikatów i sprawdzanie istnienia w porównaniu z tablicami, a także ułatwia implementację operacji na zbiorach, takich jak suma i część wspólna.

Podstawy: tworzenie i używanie zbiorów

Najpierw zobaczmy, jak utworzyć Set, dodawać i usuwać elementy, sprawdzać ich obecność oraz uzyskać wielkość zbioru.

Poniżej znajduje się podstawowy schemat tworzenia nowego Set wraz z użyciem metod: add, has, delete i size.

 1// Create a Set and demonstrate add, has, delete, and size
 2const s = new Set();
 3
 4s.add(1);
 5s.add(2);
 6s.add(2); // duplicate, ignored
 7
 8console.log(s.has(1)); // true
 9console.log(s.has(3)); // false
10
11s.delete(2);
12console.log(s.size); // 1
13
14console.log([...s]); // [1]
  • Jak pokazano w tym kodzie, Set automatycznie usuwa zduplikowane wartości prymitywne, a liczbę elementów można uzyskać za pomocą właściwości size.

Metody iteracji

Set jest iterowalny, więc możesz go przechodzić używając for...of lub forEach. Kolejność jest zgodna z kolejnością dodawania elementów.

Oto typowe sposoby użycia for...of oraz forEach.

 1// Iterate a Set with for...of and forEach
 2const s = new Set(['a', 'b', 'c']);
 3
 4for (const v of s) {
 5  console.log('for...of:', v);
 6}
 7
 8s.forEach((value, sameValue, setRef) => {
 9  // Note: second arg is same as first for Set API to match Map signature
10  console.log('forEach:', value);
11});
  • Sygnatura funkcji zwrotnej dla forEach to value, value, set (ze względu na kompatybilność z Map), ale w praktyce najczęściej używasz tylko pierwszego argumentu value.

Konwersja między tablicami a Set (przydatna do usuwania duplikatów)

Pokażemy tutaj prostą technikę usuwania duplikatów z tablicy oraz jak przekształcić Set z powrotem w tablicę.

Poniżej znajduje się przykład usuwania duplikatów z tablicy poprzez utworzenie z niej Set.

1// Deduplicate an array using Set
2const arr = [1, 2, 2, 3, 3, 3];
3const deduped = [...new Set(arr)];
4console.log(deduped); // [1, 2, 3]
5
6// Convert a Set to an array using Array.from
7const s = new Set([4, 5, 6]);
8const arrFromSet = Array.from(s);
9console.log(arrFromSet); // [4, 5, 6]
  • Ten sposób jest krótki i szybki, dlatego często stosowany do usuwania duplikatów z tablic. Jest szczególnie skuteczny dla wartości prymitywnych.

Obiekty i obsługa referencji

Obiekty w Set porównywane są przez referencję, więc różne instancje o tej samej zawartości są traktowane jako osobne elementy.

Poniższy kod pokazuje, co się dzieje po dodaniu obiektów do Set.

 1// Objects are compared by reference in a Set
 2const obj1 = { x: 1 };
 3const obj2 = { x: 1 };
 4
 5const s = new Set();
 6s.add(obj1);
 7s.add(obj2);
 8
 9console.log(s.size); // 2 (different references)
10console.log(s.has(obj1)); // true
11console.log(s.has({ x: 1 })); // false (different object)
  • Wykrywanie duplikatów dla obiektów opiera się na ich tożsamości referencyjnej, więc jeśli chcesz deduplikować według zawartości, musisz je zserializować lub dodatkowo przetworzyć.

Wartości specjalne: obsługa NaN oraz -0/+0

Set używa zasady porównywania Same-value-zero do ustalania równości wartości. Ta metoda porównywania ma następujące właściwości względem liczb:.

  • NaN jest uznawany za równy NaN.
  • Dodatnie +0 i ujemne -0 nie są rozróżniane i są traktowane jako ta sama wartość.

Dlatego, gdy dodasz te wartości do Set, pojawiają się następujące zachowania:.

 1// NaN and zero behavior in Set
 2const s = new Set();
 3
 4s.add(NaN);
 5s.add(NaN);
 6console.log(s.size); // 1 (NaN considered the same)
 7
 8s.add(+0);
 9s.add(-0);
10console.log(s.size); // still 2 (NaN + 0)
11console.log([...s]); // [NaN, 0] (order may vary but only one zero)
  • W standardowym porównaniu (NaN === NaN) wynik to false, ale w Set wszystkie wartości NaN są traktowane jako „ta sama wartość”.
  • +0 i -0 mogą być rozróżniane matematycznie, ale w Set są po prostu traktowane jako 0.
  • W efekcie w Set pozostaje tylko jedno NaN i jedno 0.
  • Zasada porównywania w Set jest podobna do Object.is, ale nie identyczna. Object.is(+0, -0) zwraca false, ale w Set są traktowane jako identyczne. Zwróć na tę różnicę uwagę.

Typowe zastosowanie: operacje na zbiorach (suma, część wspólna, różnica)

Operacje na zbiorach można zapisać bardziej przejrzyście używając Set. Poniżej przedstawiono typowe przykłady implementacji.

Oto przykłady funkcji umożliwiających union, intersection oraz difference.

 1// Set operations: union, intersection, difference
 2function union(a, b) {
 3  return new Set([...a, ...b]);
 4}
 5
 6function intersection(a, b) {
 7  return new Set([...a].filter(x => b.has(x)));
 8}
 9
10function difference(a, b) {
11  return new Set([...a].filter(x => !b.has(x)));
12}
13
14// Demo
15const A = new Set([1, 2, 3]);
16const B = new Set([3, 4, 5]);
17
18console.log('union', [...union(A, B)]); // [1,2,3,4,5]
19console.log('intersection', [...intersection(A, B)]); // [3]
20console.log('difference A\\B', [...difference(A, B)]); // [1,2]
  • Operacje na zbiorach można łatwo napisać stosując filtry i łącząc Set z tablicami. Przy pracy z dużymi zbiorami danych, wydajność O(1) metody has przyspiesza operacje.

Praktyczny przykład: znajdowanie różnic w tablicach (wykrywanie dodanych/usuniętych elementów)

Poniższy przykład demonstruje, jak użyć Set do wykrycia różnic między dwiema tablicami (starą i nową listą). Dzięki temu można wskazać, które elementy zostały dodane, a które usunięte.

 1// Find added and removed items between two arrays
 2function diffArrays(oldArr, newArr) {
 3  const oldSet = new Set(oldArr);
 4  const newSet = new Set(newArr);
 5
 6  const added = [...newSet].filter(x => !oldSet.has(x));
 7  const removed = [...oldSet].filter(x => !newSet.has(x));
 8
 9  return { added, removed };
10}
11
12const oldList = [1, 2, 3];
13const newList = [2, 3, 4, 5];
14
15console.log(diffArrays(oldList, newList));
16// { added: [4,5], removed: [1] }
  • Ta metoda jest bardzo wygodna przy wykrywaniu zmian w listach identyfikatorów, tagów oraz w podobnych przypadkach. Najprościej używać jej z wartościami prymitywnymi.

Różnice między WeakSet a Set (zarządzanie pamięcią)

WeakSet jest podobny do Set, ale używa słabych referencji, co pozwala na usuwanie jego elementów przez garbage collector. Poniżej przedstawiono podstawowe przykłady użycia WeakSet.

1// WeakSet basics (objects only, not iterable)
2const ws = new WeakSet();
3let obj = { id: 1 };
4ws.add(obj);
5
6console.log(ws.has(obj)); // true
7
8obj = null; // Now the object is eligible for GC; WeakSet won't prevent collection

WeakSet może przechowywać tylko obiekty i nie można po nim iterować. Poniżej znajdują się przykłady ograniczeń WeakSet — przechowuje tylko obiekty i nie można po nim iterować.

 1// WeakSet basics (objects only, not iterable)
 2const ws = new WeakSet();
 3
 4// --- Only objects can be added ---
 5try {
 6	ws.add(1); // number
 7} catch (e) {
 8	console.log("Error: WeakSet can only store objects. Adding a number is not allowed.");
 9}
10
11try {
12	ws.add("text"); // string
13} catch (e) {
14	console.log("Error: WeakSet can only store objects. Adding a string is not allowed.");
15}
16
17// --- WeakSet is not iterable ---
18try {
19	for (const value of ws) {
20		console.log(value);
21	}
22} catch (e) {
23	console.log("Error: WeakSet is not iterable. You cannot use for...of to loop over its elements.");
24}
25
26// --- Cannot convert to array ---
27try {
28	console.log([...ws]);
29} catch (e) {
30	console.log("Error: WeakSet cannot be converted to an array because it does not support iteration.");
31}
32
33// The object becomes eligible for garbage collection
34let obj = { id: 1 };
35ws.add(obj);
36obj = null;
  • WeakSet jest przydatny do tymczasowego śledzenia obecności obiektów, ale nie można wyliczyć jego elementów ani uzyskać rozmiaru.

Wydajność i wybór odpowiedniego zastosowania

Podejmując decyzję o użyciu Set, ważne jest zrozumienie jego wydajności oraz charakterystyki Twoich danych.

  • has, add oraz delete działają zazwyczaj ze średnią złożonością zblizoną do O(1). Dlatego w przypadkach częstego sprawdzania istnienia lub usuwania duplikatów, Set często przewyższa tablice.
  • Zachowaj ostrożność, jeśli chcesz usuwać duplikaty obiektów na podstawie ich zawartości (wartości). Ponieważ Set porównuje poprzez referencję, praktycznie poleca się stosować ID lub inne klucze albo serializować obiekty do wartości prymitywnych przed użyciem w Set, gdy konieczne jest porównanie na podstawie wartości.
  • Set jest szczególnie przydatny dla poprawy czytelności kodu przy małych lub średnich zbiorach. Z drugiej strony, jeśli obsługujesz bardzo dużą liczbę elementów lub często konwertujesz między tablicami a Set, zaleca się przeprowadzenie testów wydajnościowych.

Typowe pułapki

Set jest wygodny, ale nieznajomość jego specyfiki może prowadzić do zaskakujących zachowań. Oto typowe aspekty, na które należy zwrócić uwagę:.

  • Obiekty są porównywane przez referencję, więc nawet przy takiej samej zawartości nie są traktowane jako duplikaty.
  • Set utrzymuje kolejność dodawania, ale nie można uzyskać dostępu do elementów poprzez indeks tak jak w tablicach. Jeśli chcesz uzyskać dostęp po indeksie, najpierw przekonwertuj Set na tablicę.
  • WeakSet nie może być enumerowany i może przechowywać tylko obiekty. Zwróć uwagę, że jego zastosowania są ograniczone.
  • NaN jest traktowany jako ta sama wartość, a +0 i -0 nie są rozróżniane. To wynika z zasady porównywania Same-value-zero.

Podsumowanie

Set to wygodna struktura danych, która pozwala w intuicyjny sposób obsługiwać zbiory unikalnych wartości. Możesz jej użyć do usuwania duplikatów z tablic, szybkiego sprawdzania obecności elementów oraz łatwego implementowania operacji na zbiorach, takich jak suma i część wspólna, za pomocą prostego i czytelnego kodu.

Z drugiej strony, ponieważ obiekty są porównywane przez referencję, potrzebne są dodatkowe działania, jeśli chcesz porównywać je po zawartości.

Dzięki zrozumieniu tych właściwości i odpowiedniemu ich wykorzystaniu, Set staje się potężnym narzędziem do poprawy czytelności i łatwości utrzymania kodu.

Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.

YouTube Video