Объект `Set`

В этой статье рассматривается объект Set.

Мы объясним объект Set на практических примерах.

YouTube Video

Объект Set

Set — это встроенный объект, используемый для работы с коллекциями уникальных, не повторяющихся значений. Он позволяет проще реализовать удаление дубликатов и проверки на наличие, чем с массивами, а также облегчает выполнение операций над множествами, таких как объединение и пересечение.

Основы: создание и использование Set

Сначала рассмотрим, как создать Set, добавлять и удалять элементы, проверять наличие и получать его размер.

Ниже приведен базовый пример создания нового Set и демонстрации методов add, has, delete и свойства 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]
  • Как показано в этом коде, Set автоматически удаляет дубликаты примитивных значений, и вы можете получить количество элементов с помощью size.

Методы перебора

Set является итерируемым, поэтому вы можете перебирать его с помощью for...of или forEach. Порядок соответствует порядку добавления элементов.

Вот типичные способы использования for...of и 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});
  • Сигнатура колбэка для forEachvalue, value, set (для совместимости с Map), но на практике обычно используется только первый аргумент value.

Преобразование между массивами и Set (полезно для удаления дубликатов)

Здесь мы покажем простой способ удаления дубликатов из массива и преобразования Set обратно в массив.

Ниже приведен пример удаления дубликатов из массива с помощью передачи его через 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]
  • Этот способ короткий и быстрый, поэтому часто используется для удаления дубликатов из массивов. Он особенно эффективен для примитивных значений.

Объекты и работа со ссылками

Объекты в Set сравниваются по ссылке, поэтому разные экземпляры с одинаковым содержимым считаются разными элементами.

Следующий пример кода демонстрирует, что происходит при добавлении объектов в 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)
  • Определение дубликатов для объектов основано на идентичности ссылок, поэтому если вы хотите удалять дубликаты только по содержимому объекта, нужно сериализовать их или обрабатывать иным способом.

Особые значения: обработка NaN и -0/+0

Set использует правило сравнения Same-value-zero для определения равенства значений. Этот способ сравнения для чисел имеет следующие особенности:.

  • NaN считается равным NaN.
  • Положительный +0 и отрицательный -0 не различаются и считаются одинаковым значением.

Поэтому при добавлении этих значений в Set происходит следующее:.

 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)
  • При обычном сравнении (NaN === NaN) результат — false, но внутри Set все значения NaN считаются «одинаковыми».
  • +0 и -0 можно различить математически, но в Set оба считаются просто 0.
  • В результате в Set остается только один NaN и один 0.
  • Правило сравнения в Set похоже на Object.is, но не полностью идентично. Object.is(+0, -0) возвращает false, но в Set они считаются одинаковыми. Обратите внимание на это различие.

Типовые операции над множествами (объединение, пересечение, разность)

Операции над множествами можно выразить намного нагляднее с помощью Set. Ниже приведены распространённые примеры реализации.

Вот примеры функций для объединения, пересечения и разности множеств.

 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]
  • Операции над множествами можно реализовать просто, используя фильтры и сочетая Set с массивами. При работе с большими наборами данных производительность метода has за O(1) ускоряет такие операции.

Практический пример: поиск различий между массивами (обнаружение добавленных/удаленных элементов)

Следующий пример показывает, как использовать Set для поиска разницы между двумя массивами (старым и новым списками). Так можно определить, какие элементы были добавлены, а какие — удалены.

 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] }
  • Этот способ очень удобен для обнаружения различий в списках идентификаторов, тегов и подобных случаях. Самый простой вариант — использовать с примитивными значениями.

Разница между WeakSet и Set (управление памятью)

WeakSet похож на Set, но использует слабые ссылки, что позволяет удалять его элементы сборщиком мусора. Ниже приведены основные способы использования 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 может хранить только объекты и не поддерживает итерирование. Ниже приведены примеры ограничений WeakSet: он может хранить только объекты и не поддерживает перебор.

 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 полезен для временного отслеживания наличия объектов, но вы не можете перечислить его элементы или узнать его размер.

Производительность и выбор между вариантами использования

При решении, использовать ли Set, важно понимать его особенности производительности и характер ваших данных.

  • Методы has, add и delete обычно работают с почти O(1) производительностью в среднем. Поэтому в ситуациях, где часто требуется проверка наличия или устранение дубликатов, использование Set зачастую выгоднее массивов.
  • Будьте осторожны, если хотите удалять дубликаты объектов по их содержимому (значениям). Поскольку Set сравнивает по ссылке, на практике рекомендуется использовать ID или другие ключи, либо сериализовать объекты в примитивные значения перед использованием Set, если требуется сравнение по содержимому.
  • Set особенно полезен для повышения читаемости кода при работе с небольшими и средними коллекциями. С другой стороны, если вам приходится работать с очень большим количеством элементов или часто преобразовывать массивы и Set друг в друга, рекомендуется провести бенчмаркинг и тестирование.

Типичные подводные камни

Set — это удобно, но если не знать его особенностей, могут возникать неожиданные ситуации. Вот несколько типичных моментов, на которые стоит обратить внимание:.

  • Объекты сравниваются по ссылке, поэтому даже при одинаковом содержимом разные объекты не считаются дубликатами.
  • Set сохраняет порядок вставки, но вы не можете получить доступ к элементам по индексу, как в массивах. Если нужен доступ по индексу, сначала преобразуйте Set в массив.
  • WeakSet нельзя перебирать, и он может хранить только объекты. Учтите, что сфера применения ограничена.
  • NaN рассматривается как одно значение, а +0 и -0 не различаются. Это связано с правилом сравнения Same-value-zero.

Резюме

Set — удобная структура данных, позволяющая легко работать с наборами уникальных значений. С її помощью можно удалять дубликаты из массивов, быстро проверять наличие значений и реализовать операции над множествами, такие как объединение и пересечение, с помощью простого и читаемого кода.

С другой стороны, поскольку объекты сравниваются по ссылке, для проверки равенства по содержимому потребуются дополнительные меры.

Понимая и правильно используя эти особенности, вы сможете сделать Set мощным инструментом для повышения читаемости и поддерживаемости кода.

Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.

YouTube Video