Пост

Коллекции Set и Map в javascript

Разбираемся как Set и Map облегчают работу с данными в javascript

Массив и объект

Напомню, что объект это пара ключ - значение, где ключ должен быть строкой, а значение любого типа. Массив - это коллекция содержащая данные любого типа, при этом разрешено дублирование элементов.

Недостатки объектов и массивов

  • Ключи объекта могут быть только строкой
  • Объекты не сохраняют порядок вставки элементов
  • Массив допускает дублирование элементов

Эти недостатки устраняют структуры Set и Map.

Set

Множества Set - это коллекция для хранения уникальных значений.

Особенности:

  • Элементы коллекции могут быть любого типа кроме 0.
  • Упорядоченная коллекция элементы которой будут извлекаться в том же порядке в котором они были вставлены.
  • Повторное добавление существующего значения не меняет его положения элемента.
  • Удаление значения, а потом повторное его добавление меняет положение элемента.
  • При проверке типов используется строгое равенство.
  • Set это неиндексированная коллекция. В коллекцию можно только положить значение, но получить отдельно взятое значение нельзя.

Создать Set

Создать Set можно из любого итерируемого объекта как строка, массив и другие.

1
2
// Инициализация начальными значениями
let set = new Set(['test','test',1,true,1,[],{'o':1}]); // Set(5) { 'test', 1, true, [], { o: 1 } }

В примерах ниже будем работать с этой коллекцией.

Преобразовать в массив

Это сделать довольно просто:

1
[...set] // Получаем массив [ 'test', 1, true, [], { o: 1 } ]

Размер коллекции

1
set.size // 5 - кол-во элементов в коллекции

Проверка на наличие элемента

1
2
set.has('test1'); // false
set.has('test'); // true

Добавить элемент

Если элемент уже был в коллекции, то никакого эффекта не будет:

1
2
3
set.add(4); //Set(6) { 'test', 1, true, [], { o: 1 }, 4 }
set.has(4); // true
set.add(2).add(3).add(4); // Можно добавить сразу несколько элементов

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

Удалить элемент

1
2
3
set.delete(true); // true - значит элемент был удален
set.delete(123424); // false - такого элемента нет
set; // Set(4) { 'test', 1, [], { o: 1 } }

Удалить все элементы коллекции

1
2
set.clear()
set;  // Set(0) {}

Перебрать все элементы

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

Используем циклы:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ForEach
set.forEach((value) => {
    console.log(value);
});
// for
for(const value of set) {
    console.log(value);
}
// Получаем результат:
/*
test
1
true
[]
{ o: 1 }
 */

Работа с Set кратко

  • add(e) добавить элемент в коллекцию Set
  • delete(e) удалить элемент из коллекции Set
  • has(e) проверить наличие элемента в коллекции Set
  • clear() удаляет все элементы из коллекции Set
  • forEach(function(){}) перебор элементов коллекции Set
  • for..of перебор элементов коллекции Set
  • size количество элементов в коллекции Set
  • [...set] преобразовать в массив коллекцию Set

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

Новые методы для Set в ES2024 в 2024 году

Время не стоит на месте. С выходом Firefox 127 появились новые методы для коллекции Set.

Рассмотрим эти методы на простых примерах:

union

Метод union возвращает уникальные элементы присутствующие в обеих коллекциях.

То есть вернутся все элементы двух коллекций без повторений.

1
2
3
console.log(new Set(['t','p','g']).union(new Set(['g','k','d','t'])));
// Set(5) [ "t", "p", "g", "k", "d" ] // В данном примере вернутся только уникальные элементы присуствующие в двух коллекциях

intersection

Другая ситуация intersection вернет только те элементы, которые есть в обеих коллекциях.

1
2
console.log(new Set(['t','p','g']).intersection(new Set(['g','k','d','t'])));
// Set [ "t", "g" ] // Только 2 элемента есть в двух коллекциях, остальные отбрасываются

difference

Получим разницу отфильтровывая повторяющиеся значения.

Другими словами получим элементы которых нет во второй коллекции.

1
2
3
4
5
console.log(new Set(['t','p','g']).difference(new Set(['g','k','d','t'])));
//Set [ "p" ] // Как видим во второй коллекции по сравнею с первой нет только элемента p
// Поменяем местами коллекции           
console.log(new Set(['g','k','d','t']).difference(new Set(['t','p','g'])));
//Set [ "k", "d" ] // Теперь во второй по сравнению с первой нет d и k 

symmetricDifference

Метод возвращает элементы которые присутствуют либо в первой коллекции, либо во второй, но не в обоих одновременно.

1
2
3
4
5
6
7
8
9
10
11
12
console.log(new Set(['t','p','g']).symmetricDifference(new Set(['g','k','d','t'])));
//Set(3) [ "p", "k", "d" ]
/*
Разберем подробнее результат по каждому элементу
t - есть в обоих коллекциях
p - есть в первой коллекции
g - есть в обоих коллекциях
k - есть во второй коллекции
d - есть во второй коллекции

Итог 3 (p,k,d ) элементов нет в обоих коллекциях одновременно
 */

isSubsetOf

Вернет true или false, факт того что все элементы первой коллекции присутствуют во второй.

1
2
3
4
5
console.log(new Set(['t','p','g']).isSubsetOf(new Set(['g','k','d','t'])));
// false - элемента p не хватает во второй коллекции
// Исправим это
console.log(new Set(['t','p','g']).isSubsetOf(new Set(['g','k','d','t','p']))); 
// true - теперь все элменты есть во второй коллекции

isSupersetOf

Обратная ситуация с isSubsetOf вернет true или false в зависимости от присутствия всех элементов второй коллекции в первой.

1
2
3
4
5
console.log(new Set(['g','k','d','t','p']).isSupersetOf(new Set(['t','p','g'])));
// true - все элементы второй коллекции есть в первой
console.log(new Set(['g','k','d','t']).isSupersetOf(new Set(['t','p','g'])));
// false - элемента p нет в первой коллекции

isDisjointFrom

Вернет true или false в зависимости от пересечения между коллекциями.

1
2
3
4
console.log(new Set(['g','k','d','t']).isDisjointFrom(new Set(['t','p','g'])));
// false - немного по другому, false значит есть пересечения
console.log(new Set(['g','k','d','t']).isDisjointFrom(new Set(['a','b','c'])));
// true - значит нет пересечений

К чему пришли

Что обновилось. Добавились методы для Set:

  • union - уникальные элементы в обеих коллекциях, то есть все элементы.
  • intersection - элементы, которые есть в обеих коллекциях.
  • difference - элементы которых нет во второй коллекции.
  • symmetricDifference - элементы которые есть либо в первой коллекции, либо во второй, но не в обоих одновременно.
  • isSubsetOf - элементы первой коллекции присутствуют во второй.
  • isSupersetOf- элементы второй коллекции присутствуют во первой.
  • isDisjointFrom - нличие пересечения между коллекциями.

На мой взгляд методы значительно расширяют функционал javascript. Теперь не нужно постоянно перебирать коллекции и отфильтровывать нужные элементы.

Прямо сейчас (июль 2024) методы поддерживаются большинством браузеров.

Map

Коллекция Map - это пара ключ - значение, где ключ может быть любого типа.

Особенности:

  • Ключ пары может быть любого типа.
  • Данные могут быть извлечены в том же порядке к котором они были вставлены.
  • Map оптимизирован для частых операций и работе с большими данными.
  • Очень производительная структура данных.
  • В корне отличаются от объектов.

Создание

1
2
3
4
5
6
7
8
9
10
// Создать с уже инициализированными значениями
// Самый простой способ, массив массивов
let map = new Map([[['id',1],['name','alex']],[['id',2],['name','sergey']]]);
/*
Map(2) {
  [ 'id', 1 ] => [ 'name', 'alex' ],
  [ 'id', 2 ] => [ 'name', 'sergey' ]
}
 */
const map = new Map(); // Создать пустой Map и потом добавлять значения

Так же Map можно создать из объекта.

1
2
3
4
5
6
7
8
9
let o = {
    l: 1,
    t: 3,
    y: 5
}

let map2 = new Map(Object.entries(o));
// Таким образом гораздо удобнее сразу получить размер объекта
console.log(map2); // Map(3) { 'l' => 1, 't' => 3, 'y' => 5 }

Размер коллекции

1
map.size; // 4

Добавить значение

Повторим пример выше + добавим еще одно значение.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let map = new Map();
map.set(['id',1],['name','alex'])
   .set(['id',2],['name','sergey'])
   .set({},[4,[[function (){}]]])
   .set('2',124)

/*
Map(4) {
  [ 'id', 1 ] => [ 'name', 'alex' ],
  [ 'id', 2 ] => [ 'name', 'sergey' ],
  {} => [ 4, [ [Array] ] ],
  '2' => 124
}
 */

set() можно применять последовательно.

При записи одного и того же ключа повторно значение будет перезаписано.

Ниже работаем с примером чуть выше.

Получить значение по ключу

1
map.get('2'); //124

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

Но на практике массив или объект в качестве ключей используется редко.

Для того чтобы получить значение по не примитивному ключу нужно проделать следующее:

1
2
3
let two = ['id',2]; // Определить переменную выше
map.set(two,['name','sergey']); // Присвоить ее в map
console.log(map.get(two)); // Получить результат ['name','sergey']

Проверить существование значения

1
map.has('2'); // true // Проверить существование значения

Удалить значение

1
map.delete('2'); // true В финальном map значения уже не будет

Удалить все значения

1
map.clear(); // Теперь map пуст

Итерация по map

Самый часто используемый метод перебора

1
2
3
4
for (const [key, value] of map) {
  console.log(key);
  console.log(value);
}

Либо используя ForEach

1
2
3
4
map.forEach((value, key) => {
    console.log(key);
    console.log(value);
});

Создать из map массив

1
2
3
4
5
6
7
8
9
10
11
[...map]; // Создадим обратно массив
Array.from(map) // Или так

/*
[
  [ [ 'id', 1 ], [ 'name', 'alex' ] ],
  [ [ 'id', 2 ], [ 'name', 'sergey' ] ],
  [ {}, [ 4, [Array] ] ],
  [ '2', 124 ]
]
 */

Ключи и значения

Так же из Map можно отдельно получить ключи и значения

1
2
3
4
5
6
7
8
9
10
map.keys() // [Map Iterator] { [ 'id', 1 ], [ 'id', 2 ], {}, '2' }
map.values()
/*
[Map Iterator] {
  [ 'name', 'alex' ],
  [ 'name', 'sergey' ],
  [ 4, [ [Array] ] ],
  124
}
 */

Работа с Map кратко

  • set(e) - добавить значение в коллекцию Map
  • size - получить размер коллекции Map
  • get(e) - получить значение по ключу коллекции Map
  • has(e) - проверить существование значения коллекции Map
  • delete(e) - удалить значение коллекции Map
  • clear() - очистить коллекцию Map полностью
  • for (const [key, value] of map) - перебор коллекции Map
  • map.forEach((value, key) => {} - перебор коллекции Map
  • [...map] - создать массив из коллекции Map
  • Array.from(map) - создать массив из коллекции Map
  • Object.fromEntries(map) - создать объект из коллекции Map
  • new Map(Object.entries(map) - создать объект в коллекцию Map
  • map.keys() - получить ключи коллекции Map
  • map.values() - получить значения коллекции Map

Map или Объект

Используем Map когда:

  • Нужны специфические ключи например массив или объект.
  • Порядок элементов важен.
  • Нужна гибкость манипулирования структурой.

Если устаивает поведение объекта тогда используем его.

Задачи

Поменять местами ключи и значения

1
2
3
4
5
6
7
8
9
10
11
let swap = new Map([...map].map(el => el.reverse()));
console.log(swap);

/*
Map(4) {
  [ 'name', 'alex' ] => [ 'id', 1 ],
  [ 'name', 'sergey' ] => [ 'id', 2 ],
  [ 4, [ [Array] ] ] => {},
  124 => '2'
}
 */
Авторский пост защищен лицензией CC BY 4.0 .