Set

Об'єкт Set (множина) дає змогу зберігати унікальні значення будь-якого типу, як примітивні, так і посилання на об'єкти.

Опис

Об'єкти Set є колекціями значень. Конкретне значення в множині може зустрітись лише раз, воно є неповторним у межах колекції цієї множини. Ітерування елементів Set відбувається в порядку додання. Порядок додання відповідає порядкові, в якому кожен елемент був успішно вставлений у множину методом add() (тобто коли до виклику add() у множині не було ідентичного елемента).

Специфікація вимагає, щоб множини були реалізовані "так, щоб в середньому час доступу був сублінійним відносно числа елементів колекції". Таким чином, внутрішньо вони можуть бути представлені як геш-таблиця (з доступом O(1)), як дерево пошуку (з доступом O(log(N))) або будь-яка інша структура даних, поки складність доступу краща за O(N).

Рівність значень

Рівність значень заснована на алгоритмі SameValueZero. (Раніше використовувався алгоритм SameValue, котрий розглядав 0 і -0 як різні значення. Перевірте сумісність із браузерами.) Це означає, що значення NaN вважається рівним іншому значенню NaN (попри те, що NaN !== NaN), а всі решта значень перевіряються на рівність згідно з семантикою оператора ===.

Швидкодія

Метод has перевіряє, чи присутнє значення в множині, використовуючи підхід, що в середньому є швидшим за перевірку більшості елементів, що були до того додані до множини. Для прикладу, це в середньому швидше, ніж метод Array.prototype.includes, коли масив має length, що дорівнює значенню size множини.

Композиція множин

Об'єкт Set пропонує методи, що дають змогу компонувати множини, як це робиться у математиці. Серед цих методів:

Метод Тип поверненого значення Математичний еквівалент Діаграма Венна
A.difference(B) Set ABA\setminus B Діаграма Венна, в якій два кола перетинаються. Різниця між A і B – це та частина A, що не перетинається з B.
A.intersection(B) Set ABA\cap B Діаграма Венна, в якій два кола перетинаються. Перетин між A і B – це та частина, якою вони перетинаються.
A.symmetricDifference(B) Set (AB)(BA)(A\setminus B)\cup(B\setminus A) Діаграма Венна, в якій два кола перетинаються. Симетрична різниця між A і B – це область, охоплена одним з кіл, але не двома.
A.union(B) Set ABA\cup B Діаграма Венна, в якій два кола перетинаються. Симетрична різниця між A і B – це область, охоплена одним або двома колами.
A.isDisjointFrom(B) Boolean AB=A\cap B = \empty Діаграма Венна з двома колами. A i B є неперетинними, тому що ці два кола не мають області перетину.
A.isSubsetOf(B) Boolean ABA\subseteq B Діаграма Венна з двома колами. A є підмножиною B, тому що A повністю вміщена в B.
A.isSupersetOf(B) Boolean ABA\supseteq B Діаграма Венна з двома колами. A є надмножиною B, тому що B повністю вміщена в A.

Щоб бути більш узагальненими, ці методи приймають не лише об'єкти Set, але й будь-що, що є множиноподібним.

Множиноподібні об'єкти

Усі методи композиції множин вимагають того, щоб значення this було справжнім примірником Set, але їх аргументи повинні бути хоча б множиноподібними. Множиноподібний об'єкт – це об'єкт, що має наступне:

  • Властивість size, що містить число.
  • Метод has(), який приймає елемент і повертає булеве значення.
  • Метод keys(), який повертає ітератор елементів множини.

Наприклад, об'єкти Map є множиноподібними, оскільки вони також мають size, has() і keys(), тому вони поводяться неначе множини ключів, коли використовуються у методах множин:

const a = new Set([1, 2, 3]);
const b = new Map([
  [1, "один"],
  [2, "два"],
  [4, "чотири"],
]);
console.log(a.union(b)); // Set(4) {1, 2, 3, 4}

Примітка: Протокол множиноподібності закликає для видачі елементів метод keys(), а не [@@iterator](). Це зроблено для того, щоб відображення були дієвими множиноподібними об'єктами, адже в випадку відображень ітератор видає записи, проте метод has() приймає ключі. Масиви не є множиноподібними, тому що не мають метода has() і властивості size, а їх метод keys() видає індекси, а не елементи. Об'єкти WeakSet також не є множиноподібними, оскільки не мають метода keys().

Set-подібні API браузера

Set-подібні об'єкти браузера (або "множиноподібні об'єкти") - це інтерфейси API Вебу, що з багатьох боків поводяться подібно до Set.

Як і в Set, елементи можуть бути ітеровані в тому ж порядку, в якому вони додані до такого об'єкта. Також Set-подібні об'єкти та Set мають властивості та методи, що поділяють однакові назви та поведінку. Проте, на відміну від Set, вони дозволяють лише певний наперед визначений тип для кожного зі своїх елементів.

Дозволені типи задані у визначенні специфікації IDL. Наприклад, GPUSupportedFeatures – це Set-подібний об'єкт, що повинен використовувати рядки як ключі та значення. Це визначено в специфікації IDL нижче:

interface GPUSupportedFeatures {
  readonly setlike<DOMString>;
};

Set-подібні об'єкти – або доступні лише для зчитування, або також і для запису (дивіться ключове слово readonly в IDL вище).

Ці методи та властивості мають таку ж логіку, як рівносильні аналоги в Set, окрім обмеження щодо типів елементів.

Нижче – приклади Set-подібних браузерних об'єктів лише для зчитування:

А це – приклади записних Set-подібних браузерних об'єктів:

Конструктор

Set()

Створює новий об'єкт Set.

Статичні властивості

Set[@@species]

Функція-конструктор, що використовується для створення похідних об'єктів.

Властивості примірника

Ці властивості означені на Set.prototype і є спільними для всіх примірників Set.

Set.prototype.constructor

Функція-конструктор, що створила об'єкт-примірник. Для примірників Set початковим значенням є конструктор Set.

Set.prototype.size (розмір)

Повертає кількість значень, присутніх в об'єкті Set.

Set.prototype[@@toStringTag]

Початкове значення властивості @@toStringTag – рядок "Set". Ця властивість використовується в Object.prototype.toString().

Методи примірника

Set.prototype.add() (додати)

Додає новий елемент зі вказаним значенням до об'єкта Set, якщо елемента з таким само значенням іще в Set немає.

Set.prototype.clear() (очистити)

Усуває з об'єкта Set всі значення.

Set.prototype.delete() (видалити)

Усуває елемент, пов'язаний із value, та повертає булеве значення, що вказує, чи був елемент успішно усунутий. Після цього Set.prototype.has(value) поверне false.

Set.prototype.difference()

Приймає множину та повертає нову множину, що містить елементи, присутні в поточній множині, але відсутні в переданій.

Set.prototype.entries() (записи)

Повертає новий об'єкт-ітератор, що містить масив із [value, value] для кожного елемента в об'єкті Set, у порядку їх додання. Це подібно до об'єкта Map, якби ключ кожного запису був би водночас власним значенням.

Set.prototype.forEach() (для кожного)

Один раз викликає callbackFn для кожного значення, присутнього в об'єкті Set, у порядку їх додання. Якщо наданий параметр thisArg, то він використовуватиметься при кожному виклику callbackFn як значення this.

Set.prototype.has() (має)

Повертає булеве значення, що вказує, чи є елемент із даним значенням в об'єкті Set.

Set.prototype.intersection()

Приймає множину та повертає нову множину, що вміщає елементи, присутні як у поточній множині, так і в переданій.

Set.prototype.isDisjointFrom()

Приймає множину та повертає булеве значення, яке вказує на те, чи відсутні в поточної та переданої множини спільні елементи.

Set.prototype.isSubsetOf()

Приймає множину та повертає булеве значення, яке вказує на те, чи всі елементи поточної множини присутні в переданій.

Set.prototype.isSupersetOf()

Приймає множину та повертає булеве значення, яке вказує на те, чи всі елементи переданої множини присутні в поточній.

Set.prototype.keys() (ключі)

Псевдонім для Set.prototype.values().

Set.prototype.symmetricDifference()

Приймає множину та повертає нову множину, що вміщає елементи, присутні в поточній множині або в переданій, але не в них обох.

Set.prototype.union()

Приймає множину та повертає нову множину, що вміщає елементи, присутні в одній з множин або в них обох.

Set.prototype.values() (значення)

Повертає новий об'єкт-ітератор, що видає значення для кожного елемента в об'єкті Set, у порядку їх додання.

Set.prototype@@iterator

Повертає новий об'єкт-ітератор, що видає значення для кожного елемента в об'єкті Set, у порядку їх додання.

Приклади

Використання об'єкта Set

const mySet1 = new Set();

mySet1.add(1); // Set(1) { 1 }
mySet1.add(5); // Set(2) { 1, 5 }
mySet1.add(5); // Set(2) { 1, 5 }
mySet1.add("якийсь текст"); // Set(3) { 1, 5, 'якийсь текст' }
const o = { a: 1, b: 2 };
mySet1.add(o);

mySet1.add({ a: 1, b: 2 }); // o посилається на інший об'єкт, тому це нормально

mySet1.has(1); // true
mySet1.has(3); // false, оскільки 3 не додавали до множини
mySet1.has(5); // true
mySet1.has(Math.sqrt(25)); // true
mySet1.has("Якийсь Текст".toLowerCase()); // true
mySet1.has(o); // true

mySet1.size; // 5

mySet1.delete(5); // усуває 5 з множини
mySet1.has(5); // false, 5 була усунута

mySet1.size; // 4, оскільки одне значення щойно було усунуто

mySet1.add(5); // Set(5) { 1, 'якийсь текст', {...}, {...}, 5 } - видалений раніше елемент буде доданий заново; він не збереже позиції, що мав до видалення

console.log(mySet1); // Set(5) { 1, "якийсь текст", {…}, {…}, 5 }

Ітерування множин

Ітерування множин обходить елементи в порядку їх додавання.

for (const item of mySet1) {
  console.log(item);
}
// 1, "якийсь текст", { "a": 1, "b": 2 }, { "a": 1, "b": 2 }, 5

for (const item of mySet1.keys()) {
  console.log(item);
}
// 1, "якийсь текст", { "a": 1, "b": 2 }, { "a": 1, "b": 2 }, 5

for (const item of mySet1.values()) {
  console.log(item);
}
// 1, "якийсь текст", { "a": 1, "b": 2 }, { "a": 1, "b": 2 }, 5

// тут ключі збігаються зі значеннями
for (const [key, value] of mySet1.entries()) {
  console.log(key);
}
// 1, "якийсь текст", { "a": 1, "b": 2 }, { "a": 1, "b": 2 }, 5

// За допомогою Array.from перетворює об'єкт Set на об'єкт Array
const myArr = Array.from(mySet1); // [1, "якийсь текст", {"a": 1, "b": 2}, {"a": 1, "b": 2}, 5]

// наступне також запрацює, якщо запустити в контексті документа HTML
mySet1.add(document.body);
mySet1.has(document.querySelector("body")); // true

// перетворення між Set та Array
const mySet2 = new Set([1, 2, 3, 4]);
console.log(mySet2.size); // 4
console.log([...mySet2]); // [1, 2, 3, 4]

// перетин можна імітувати за допомогою
const intersection = new Set([...mySet1].filter((x) => mySet2.has(x)));

// різницю можна імітувати за допомогою
const difference = new Set([...mySet1].filter((x) => !mySet2.has(x)));

// Обійти записи множини за допомогою forEach()
mySet2.forEach((value) => {
  console.log(value);
});
// 1
// 2
// 3
// 4

Реалізація базових дій з множинами

// Чи є надмножиною
function isSuperset(set, subset) {
  for (const elem of subset) {
    if (!set.has(elem)) {
      return false;
    }
  }
  return true;
}

// Об'єднання
function union(setA, setB) {
  const _union = new Set(setA);
  for (const elem of setB) {
    _union.add(elem);
  }
  return _union;
}

// Перетин
function intersection(setA, setB) {
  const _intersection = new Set();
  for (const elem of setB) {
    if (setA.has(elem)) {
      _intersection.add(elem);
    }
  }
  return _intersection;
}

// Симетрична різниця
function symmetricDifference(setA, setB) {
  const _difference = new Set(setA);
  for (const elem of setB) {
    if (_difference.has(elem)) {
      _difference.delete(elem);
    } else {
      _difference.add(elem);
    }
  }
  return _difference;
}

// Різниця
function difference(setA, setB) {
  const _difference = new Set(setA);
  for (const elem of setB) {
    _difference.delete(elem);
  }
  return _difference;
}

// Приклади
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([2, 3]);
const setC = new Set([3, 4, 5, 6]);

isSuperset(setA, setB); // повертає true
union(setA, setC); // повертає Set {1, 2, 3, 4, 5, 6}
intersection(setA, setC); // повертає Set {3, 4}
symmetricDifference(setA, setC); // повертає Set {1, 2, 5, 6}
difference(setA, setC); // повертає Set {1, 2}

Зв'язок з масивами

const myArray = ["value1", "value2", "value3"];

// Використати звичайний конструктор Set, щоб перетворити масив на множину
const mySet = new Set(myArray);

mySet.has("значення1"); // повертає true

// Використати синтаксис розгортання, щоб перетворити множину на масив
console.log([...mySet]); // Покаже точно такий самий масив, як myArray

Усунення дублікатів з масиву

// Використання для усунення дублікатів з масиву
const numbers = [2, 13, 4, 4, 2, 13, 13, 4, 4, 5, 5, 6, 6, 7, 5, 32, 13, 4, 5];

console.log([...new Set(numbers)]); // [2, 13, 4, 5, 6, 7, 32]

Зв'язок із рядками

// Чутливо до регістру (множина буде містити "F" і "f" окремо)
new Set("Firefox"); // Set(7) [ "F", "i", "r", "e", "f", "o", "x" ]

// Пропуск дублікатів ("f" зустрічається в рядку двічі, але множина буде містити лише одне входження)
new Set("firefox"); // Set(6) [ "f", "i", "r", "e", "o", "x" ]

Використання множини для пересвідчення щодо унікальності всіх значень у списку

const array = Array.from(document.querySelectorAll("[id]")).map((e) => e.id);

const set = new Set(array);
console.assert(set.size === array.length);

Специфікації

Сумісність із браузерами

desktop mobile server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
Set
Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
@@iterator Chrome Full support 43
Edge Full support 12
Firefox Full support 36
Internet Explorer No support No
Opera Full support 30
Safari Full support 9
WebView Android Full support 43
Chrome Android Full support 43
Firefox for Android Full support 36
Opera Android Full support 30
Safari on iOS Full support 9
Samsung Internet Full support 4.0
Deno Full support 1.0
Node.js Full support 0.12.0
@@species Chrome Full support 51
Edge Full support 13
Firefox Full support 41
Internet Explorer No support No
Opera Full support 38
Safari Full support 10
WebView Android Full support 51
Chrome Android Full support 51
Firefox for Android Full support 41
Opera Android Full support 41
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.5.0
Set() constructor Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
new Set(iterable)
Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer No support No
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
Set() without new throws
Chrome Full support 38
Edge Full support 12
Firefox Full support 42
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 42
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
new Set(null)
Chrome Full support 38
Edge Full support 12
Firefox Full support 37
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 37
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
add Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Partial support 11
footnote
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
clear Chrome Full support 38
Edge Full support 12
Firefox Full support 19
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 19
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
delete Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
entries Chrome Full support 38
Edge Full support 12
Firefox Full support 24
Internet Explorer No support No
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 24
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
forEach Chrome Full support 38
Edge Full support 12
Firefox Full support 25
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 25
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
has Chrome Full support 38
Edge Full support 12
Firefox Full support 13
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 14
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
Key equality for -0 and 0
Chrome Full support 38
Edge Full support 12
Firefox Full support 29
Internet Explorer No support No
Opera Full support 25
Safari Full support 9
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 29
Opera Android Full support 25
Safari on iOS Full support 9
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 4.0.0
size Chrome Full support 38
Edge Full support 12
Firefox Full support 19
footnote
Internet Explorer Full support 11
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 19
footnote
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0
values Chrome Full support 38
Edge Full support 12
Firefox Full support 24
Internet Explorer No support No
Opera Full support 25
Safari Full support 8
WebView Android Full support 38
Chrome Android Full support 38
Firefox for Android Full support 24
Opera Android Full support 25
Safari on iOS Full support 8
Samsung Internet Full support 3.0
Deno Full support 1.0
Node.js Full support 0.12.0

Дивіться також