Sposoby tworzenia zestawu w JavaScript?

83

W Eloquent JavaScript, Rozdział 4, zestaw wartości jest tworzony przez utworzenie obiektu i przechowywanie wartości jako nazw właściwości, przypisując dowolne wartości (np. True) jako wartości właściwości. Aby sprawdzić, czy wartość jest już zawarta w zbiorze, inużywany jest operator:

var set = {};

if (!'Tom' in set) { 
  set.Tom = true;
}

Czy to idiomatyczny JavaScript? Czy nie użyłbyś tablicy jeszcze lepiej?

var set = [];

if (!'Tom' in set) { 
  set.push = 'Tom';
}
helpermethod
źródło
1
Jak można się spodziewać tablicy, dla której 'Tom' in setma wyglądać prawda? Wygląda na to, że masz błędne założenia na temat czegoś , a ja próbuję się dowiedzieć, o czym .
10
FYI, trzeba parens: if(!('Tom' in set)). Obecnie to znaczy false in setod !'Tom' === false.
pimvdb
ES6 ma sety, patrz odpowiedź Johna poniżej
Ben Taliadoros

Odpowiedzi:

101

Zestawy są teraz dostępne w ES2015 (aka ES6, czyli ECMAScript 6). ES6 to obecny standard JavaScript od czerwca 2015.

ECMAScript 6 ma strukturę danych Set, która działa dla dowolnych wartości, jest szybki i poprawnie obsługuje NaN. - Axel Rauschmayer , Exploring ES6

Pierwsze dwa przykłady z książki Axela Rauschmayera Exploring ES6 :

Zarządzanie pojedynczymi elementami:

> let set = new Set();
> set.add('red')

> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false

Określenie rozmiaru zestawu i jego wyczyszczenie:

> let set = new Set();
> set.add('red')
> set.add('green')

> set.size
2
> set.clear();
> set.size
0

Chciałbym sprawdzić Exploring ES6, jeśli chcesz dowiedzieć się więcej o zestawach w JavaScript. Książkę można czytać online bezpłatnie, ale jeśli chcesz wesprzeć autora, dr. Axela Rauschmayera, możesz ją kupić za około 30 dolarów.

Jeśli chcesz teraz używać Sets i ES6, możesz użyć Babel , transpilera ES6 do ES5 i jego polyfillów.

Edycja: Od 6 czerwca 2017 większość głównych przeglądarek ma pełną obsługę zestawu w swoich najnowszych wersjach (z wyjątkiem IE 11). Oznacza to, że możesz nie potrzebować babel, jeśli nie chcesz obsługiwać starszych przeglądarek. Jeśli chcesz zobaczyć kompatybilność w różnych przeglądarkach, w tym w bieżącej przeglądarce, sprawdź tabelę kompatybilności Kangax ES6 .

EDYTOWAĆ:

Tylko wyjaśnienie dotyczące inicjalizacji. Zestawy mogą przyjmować dowolną synchroniczną iterację w swoim konstruktorze. Oznacza to, że mogą przyjmować nie tylko tablice, ale także ciągi i iteratory. Weźmy na przykład następującą tablicę i inicjalizację ciągu zestawu:

const set1 = new Set(['a','a','b','b','c','c']);
console.log(...set1);
console.log(set1.size);
const set2 = new Set("aabbcc");
console.log(...set2);
console.log(set2.size);

Oba wyjścia tablicy i łańcucha są takie same. Zauważ, że ...set1jest to składnia rozkładu . Wydaje się, że każdy element iterowalny jest dodawany jeden po drugim do zestawu, więc ponieważ zarówno tablica, jak i łańcuch mają te same elementy, a elementy są w tej samej kolejności, zestaw jest tworzony tak samo. Inną rzeczą, na którą należy zwrócić uwagę w przypadku zestawów, jest to, że podczas iteracji po nich kolejność iteracji jest zgodna z kolejnością wstawiania elementów do zestawu. Oto przykład iteracji na zestawie:

const set1 = new Set(['a','a','b','b','c','c']);
for(const element of set1) {
  console.log(element);
}

Ponieważ możesz użyć dowolnej iteracji do zainicjowania zestawu, możesz nawet użyć iteratora z funkcji generatora . Oto dwa takie przykłady inicjalizacji iteratora, które dają ten sam wynik:

// a simple generator example
function* getLetters1 () {
  yield 'a';
  yield 'a';
  yield 'b';
  yield 'b';
  yield 'c';
  yield 'c';
}

// a somewhat more commonplace generator example
// with the same output as getLetters1.
function* getLetters2 (letters, repeatTimes) {
  for(const letter of letters) {
    for(let i = 0; i < repeatTimes; ++i) { 
      yield letter;
    }
  }
}

console.log("------ getLetters1 ------");
console.log(...getLetters1());
const set3 = new Set(getLetters1());
console.log(...set3);
console.log(set3.size);

console.log("------ getLetters2 ------");
console.log(...getLetters2('abc', 2));
const set4 = new Set(getLetters2('abc', 2));
console.log(...set4);
console.log(set4.size);

Funkcje generatora podane w tych przykładach można po prostu napisać tak, aby się nie powtarzały, ale jeśli funkcja generatora jest bardziej skomplikowana i jeśli poniższe elementy nie wpływają zbyt negatywnie na wydajność, można użyć metody Set, aby pomóc uzyskać tylko wartości z generatora, który nie nie powtarzam.

Jeśli chcesz dowiedzieć się więcej o zestawach bez czytania rozdziału jego książki doktora Rauschmayera, zajrzyj do dokumentacji MDN na Set . MDN również więcej przykładów iteracji przez zestaw takich jak używanie forEachi stosowania .keys, .valuesi .entriesmetod. MDN zawiera również przykłady, takie jak suma zestawów, przecięcie zestawów, różnica zestawów, symetryczna różnica między zbiorami i sprawdzanie nadzbiorów. Miejmy nadzieję, że większość tych operacji będzie dostępna w JavaScript bez konieczności budowania własnych funkcji, które je obsługują. W rzeczywistości istnieje ta propozycja TC39 dla nowych metod Set, która, miejmy nadzieję, powinna dodać następujące metody do Set in JavaScript w pewnym momencie w przyszłości, jeśli propozycja osiągnie etap 4:

  • Set.prototype.intersection (iterable) - metoda tworzy nową instancję Set poprzez operację set intersection.
  • Set.prototype.union (iterable) - metoda tworzy nową instancję Set poprzez operację set union.
  • Set.prototype.difference (iterable) - metoda tworzy nowy Set bez elementów obecnych w iterable.
  • Set.prototype.symmetricDifference (iterable) - zwraca zestaw elementów znalezionych tylko w this lub w iterowalnym.
  • Set.prototype.isSubsetOf (iterowalne)
  • Set.prototype.isDisjointFrom (iterowalne)
  • Set.prototype.isSupersetOf (iterowalne)
Jan
źródło
Również same Set są iterowalne, więc możesz zainicjować zestawy z innymi zestawami, aby wykonać kopię lub zrobić coś takiego jak nowy zestaw ([... setA, ... setB]) dla operacji sumowania.
John
1
@LanceKind Dodałem kilka dodatkowych informacji, w tym pokazanie, że można inicjować zestawy z dowolnymi iterowalnymi, nie tylko tablicami.
John
32

Używam obiektów dict jako zestawów. Działa to z ciągami i liczbami, ale przypuszczam, że spowodowałoby to problemy, gdybyś chciał mieć zestaw obiektów używających niestandardowych operatorów równości i porównania:

Tworzenie zestawu:

var example_set = 
{
    'a':true,
    'b':true,
    'c':true
}

Testowanie pod kątem włączenia do zestawu

if( example_set['a'] ){
    alert('"a" is in set');
}

Dodanie elementu do zestawu

example_set['d'] = true;

Usunięcie elementu z zestawu

delete example_set['a'];

Chris Dutrow
źródło
1
Kod, nad którym pracowałem, korzystał ze starszej wersji silnika, która nie miała wsparcia Set. To pomogło.
Abhijith Madhav
16

Zestawy nie pozwalają na zduplikowane wpisy i zazwyczaj nie gwarantują wstępnie zdefiniowanej kolejności. Tablice robią obie te rzeczy, naruszając w ten sposób to, co to znaczy być zbiorem (chyba że wykonasz dodatkowe sprawdzenia).

Jon Newmuis
źródło
2
Array nie filtruje zduplikowanych wpisów ... na przykład arr.push ({id: 1, name: "Jake"}) jak NSSet w Objective-C :)
iTux
Jeśli weźmiesz to mot-a-mot, nie. Ale możesz po prostu użyć id jako klucza tablicy (mapy). arr[id] = {"name": "Jake"};
Buffalo
11

Pierwszy sposób to idiomatyczny JavaScript.

Za każdym razem, gdy chcesz zapisać parę klucz / wartość, musisz użyć obiektu JavaScript. Jeśli chodzi o tablice, istnieje kilka problemów:

  1. Indeks jest wartością liczbową.

  2. Nie ma łatwego sposobu na sprawdzenie, czy wartość znajduje się w tablicy bez przechodzenia przez pętlę.

  3. Zestaw nie pozwala na duplikaty. Tablica tak.

Justin Niessner
źródło
Co najlepiej byłoby przypisać do wartości nieruchomości?
helpermethod
1
Możliwe jest usunięcie zduplikowanych elementów z tablic JavaScript. stackoverflow.com/a/12166248/975097
Anderson Green
9

Jeśli chcesz utworzyć zestaw z tablicy, po prostu wykonaj:

let arr = [1, 1, 2, 1, 3];
let mySet = new Set(arr); // Set { 1, 2, 3 }

Jest to składnia cukru, która bardzo mi się spodobała podczas programowania w Pythonie, więc cieszę się, że ES6 w końcu umożliwiło zrobienie tego samego.

UWAGA: wtedy zdaję sobie sprawę, że to, co powiedziałem, nie odpowiadało bezpośrednio na twoje pytanie. Powodem tego "hackowania" w ES5 jest to, że czas wyszukiwania w obiekcie za pomocą kluczy jest znacznie szybszy (O (1)) niż w tablicy (O (n)). W aplikacjach o krytycznym znaczeniu dla wydajności można poświęcić tę odrobinę czytelności lub intuicji dla lepszej wydajności.

Ale hej, witaj w 2017 roku, w którym możesz teraz używać odpowiedniego zestawu we wszystkich głównych nowoczesnych przeglądarkach!

benjaminz
źródło
6

Zestawy w ES6/ ES2015:

ES6/ ES2015ma teraz wbudowane zestawy. Zestaw to struktura danych, która umożliwia przechowywanie unikatowych wartości dowolnego typu, niezależnie od tego, czy są to wartości pierwotne, czy odniesienia do obiektów. Zestaw można zadeklarować za pomocą ES6wbudowanego konstruktora zestawu w następujący sposób:

const set = new Set([1, 2, 3, 4, 5]);

Podczas tworzenia zestawu za pomocą konstruktora Set nasz nowo utworzony obiekt set dziedziczy po Set.prototype. Ma to wiele pomocniczych metod i właściwości. Pozwala to łatwo wykonać następujące czynności:

Przykład:

const set = new Set([1, 2, 3, 4, 5]);

// checkout the size of the set
console.log('size is: ' + set.size);

// has method returns a boolean, true if the item is in the set
console.log(set.has(1));

// add a number
set.add(6);

// delete a number
set.delete(1);

// iterate over each element using a callback
set.forEach((el) => {
  console.log(el);
});

// remove all the entries from the set
set.clear();

Zgodność z przeglądarkami:

Wszystkie główne przeglądarki w pełni obsługują teraz zestawy z wyjątkiem IE, w których brakuje niektórych funkcji. Dokładne informacje można znaleźć w dokumentacji mdn .

Willem van der Veen
źródło
@Velojet: Ok, w porządku.
kjhughes,
3

Istnieją dwa problemy z używaniem gołych obiektów javascript do emulowania zestawów: po pierwsze, obiekt może mieć dziedziczoną właściwość, która wkręcałaby operator "in", a po drugie, w ten sposób można przechowywać tylko wartości skalarne, dzięki czemu zestaw obiektów nie jest możliwy. Dlatego realistyczna implementacja zestawów powinna zapewniać metody, adda containszamiast inprzypisań prostych i właściwości.

Georg
źródło
@kojiro: obiekty są konwertowane na ciągi znaków, gdy są używane jako klucze:set={};set[{x:1}]=123;alert(set[{z:99}])
georg,
3

Możesz wypróbować Buckets , jest biblioteką struktury danych javascript i ma wszystko, czego potrzebujesz do manipulowania zestawami.

Daniel
źródło
czy zapewnia metodę addAll (tablica)?
jorrebor
1

Podstawowe tworzenie i używanie obiektu Set 🔷

let mySet = new Set()

mySet.add(2)         // Set {2}
mySet.add(7)         // Set {2, 7}
mySet.add(7)         // Set {2, 7}
mySet.add('my text') // Set {2, 7, 'my text'}
let myObj = { a: 1, b: 2 }
mySet.add(myObj)     // Set {2, 7, 'my text', {...}}
mySet.has(2)         // true
mySet.has(myObj)     // true
mySet.size           // 4

Iteracja

for (let item of mySet) console.log(item)  // 2, 7, 'my text', {a:1, b:2}
mySet.forEach(value => console.log(value)) // 2, 7, 'my text', {a:1, b:2}

Konwertuj na tablicę

var myArr = Array.from(mySet)             // [2, 7, 'my text', {a:1, b:2}]

❕ Najbardziej charakterystyczną cechą Oferty zestawów jest to, że każda wartość w obiekcie zestawu musi być niepowtarzalna. Nie możesz więc dodawać zduplikowanych wartości.

Hasan Sefa Ozalp
źródło