Коллекции 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'
}
*/