До сих пор мы узнали о следующих сложных структурах данных:
Объекты используются для хранения коллекций с ключами.
Массивы используются для хранения упорядоченных коллекций.
Но для реальной жизни этого недостаточно. Вот почему Map
и Set
также существуют.
Карта — это набор элементов данных с ключами, как и Object
. Но главное отличие в том, что Map
допускает использование ключей любого типа.
Методы и свойства:
new Map()
– создает карту.
map.set(key, value)
– сохраняет значение по ключу.
map.get(key)
– возвращает значение по ключу, undefined
, если key
не существует в карте.
map.has(key)
– возвращает true
если key
существует, в противном случае — false
.
map.delete(key)
– удаляет элемент (пару ключ/значение) по ключу.
map.clear()
– удаляет все с карты.
map.size
– возвращает текущее количество элементов.
Например:
пусть карта = новая карта(); map.set('1', 'str1'); // строковый ключ map.set(1, 'число1'); // цифровой ключ map.set(истина, 'bool1'); // логический ключ // помните обычный Объект? он преобразует ключи в строку // Карта сохраняет тип, поэтому эти два варианта различны: предупреждение(map.get(1)); // 'номер1' предупреждение(map.get('1')); // 'стр1' предупреждение(карта.размер); // 3
Как мы видим, в отличие от объектов, ключи не преобразуются в строки. Возможен любой тип ключа.
map[key]
— неправильный способ использования Map
Хотя map[key]
также работает, например, мы можем установить map[key] = 2
, это рассматривает map
как простой объект JavaScript, поэтому подразумевает все соответствующие ограничения (только строковые/символьные ключи и т. д.).
Поэтому нам следует использовать методы map
: set
, get
и так далее.
Карта также может использовать объекты в качестве ключей.
Например:
пусть Джон = {имя: "Джон"}; // для каждого пользователя сохраним количество посещений пусть visitsCountMap = новая карта(); // Джон — ключ к карте VisitsCountMap.set(Джон, 123); Предупреждение( visitsCountMap.get(john) ); // 123
Использование объектов в качестве ключей — одна из наиболее заметных и важных функций Map
. То же самое не относится к Object
. Строка в качестве ключа в Object
— это нормально, но мы не можем использовать другой Object
в качестве ключа в Object
.
Давайте попробуем:
пусть Джон = {имя: "Джон"}; пусть Бен = {имя: "Бен"}; пусть visitsCountObj = {}; // пытаемся использовать объект visitsCountObj[бен] = 234; // пытаемся использовать объект Ben в качестве ключа visitsCountObj[john] = 123; // попытаемся использовать объект Джона в качестве ключа, объект Бена будет заменен // Вот что написано! alert( visitsCountObj["[объект объекта]"] ); // 123
Поскольку visitsCountObj
является объектом, он преобразует все ключи Object
, такие как john
и ben
выше, в одну и ту же строку "[object Object]"
. Определенно не то, что мы хотим.
Как Map
сравнивает ключи
Для проверки ключей на эквивалентность Map
использует алгоритм SameValueZero. Это примерно то же самое, что и строгое равенство ===
, но разница в том, что NaN
считается равным NaN
. Таким образом, NaN
также можно использовать в качестве ключа.
Этот алгоритм нельзя изменить или настроить.
Цепочка
Каждый вызов map.set
возвращает саму карту, поэтому мы можем «связать» вызовы:
map.set('1', 'str1') .set(1, 'число1') .set(правда, 'bool1');
Для цикла по map
существует 3 метода:
map.keys()
– возвращает итерацию для ключей,
map.values()
– возвращает итерацию значений,
map.entries()
– возвращает итерацию для записей [key, value]
, она используется по умолчанию в for..of
.
Например:
пусть рецептMap = новая карта([ ['огурец', 500], ['помидоры', 350], ['лук', 50] ]); // перебираем ключи (овощи) for (пусть овощ рецептаMap.keys()) { оповещение(овощи); // огурец, помидоры, лук } // перебираем значения (суммы) for (пусть количество рецептовMap.values()) { оповещение (сумма); // 500, 350, 50 } // перебираем записи [ключ, значение] for (let вход в editorMap) { // то же самое, что и в editorMap.entries() предупреждение(запись); // огурец,500 (и так далее) }
Порядок вставки используется
Итерация выполняется в том же порядке, в котором были вставлены значения. Map
сохраняет этот порядок, в отличие от обычного Object
.
Кроме того, Map
имеет встроенный метод forEach
, аналогичный Array
:
// запускает функцию для каждой пары (ключ, значение) рецептMap.forEach((значение, ключ, карта) => { alert(`${key}: ${value}`); // огурец: 500 и т. д. });
Когда Map
создана, мы можем передать массив (или другой итерируемый объект) с парами ключ/значение для инициализации, например:
// массив пар [ключ, значение] пусть карта = новая карта([ ['1', 'str1'], [1, 'число1'], [правда, 'bool1'] ]); предупреждение(map.get('1')); // строка1
Если у нас есть простой объект и мы хотим создать на его основе Map
, мы можем использовать встроенный метод Object.entries(obj), который возвращает массив пар ключ/значение для объекта именно в этом формате.
Итак, мы можем создать карту из такого объекта:
пусть объект = { имя: «Джон», возраст: 30 }; пусть карта = новая карта(Object.entries(obj)); предупреждение(map.get('имя')); // Джон
Здесь Object.entries
возвращает массив пар ключ/значение: [ ["name","John"], ["age", 30] ]
. Это то, что нужно Map
.
Мы только что увидели, как создать Map
из простого объекта с помощью Object.entries(obj)
.
Существует метод Object.fromEntries
, который делает обратное: учитывая массив пар [key, value]
, он создает из них объект:
пусть цены = Object.fromEntries([ ['банан', 1], ['оранжевый', 2], ['мясо', 4] ]); // теперь цены = {банан: 1, апельсин: 2, мясо: 4 } оповещение(цены.оранжевый); // 2
Мы можем использовать Object.fromEntries
, чтобы получить простой объект из Map
.
Например, мы храним данные в Map
, но нам нужно передать их стороннему коду, который ожидает простой объект.
Вот так:
пусть карта = новая карта(); map.set('банан', 1); map.set('оранжевый', 2); map.set('мясо', 4); пусть obj = Object.fromEntries(map.entries()); // создаем простой объект (*) // сделанный! // obj = { банан: 1, апельсин: 2, мясо: 4 } оповещение (obj.orange); // 2
Вызов map.entries()
возвращает итерацию пар ключ/значение точно в правильном формате для Object.fromEntries
.
Мы также могли бы сделать строку (*)
короче:
пусть obj = Object.fromEntries(карта); // опускаем .entries()
Это то же самое, поскольку Object.fromEntries
ожидает в качестве аргумента итерируемый объект. Не обязательно массив. И стандартная итерация для map
возвращает те же пары ключ/значение, что и map.entries()
. Таким образом, мы получаем простой объект с теми же ключами/значениями, что и map
.
Set
— это коллекция особого типа — «набор значений» (без ключей), где каждое значение может встречаться только один раз.
Его основные методы:
new Set([iterable])
— создает набор, и если предоставляется iterable
объект (обычно массив), копирует значения из него в набор.
set.add(value)
— добавляет значение, возвращает сам набор.
set.delete(value)
– удаляет значение, возвращает true
, если value
существовало на момент вызова, в противном случае — false
.
set.has(value)
– возвращает true
, если значение существует в наборе, в противном случае — false
.
set.clear()
– удаляет все из набора.
set.size
– количество элементов.
Основная особенность заключается в том, что повторные вызовы set.add(value)
с одним и тем же значением ничего не делают. По этой причине каждое значение появляется в Set
только один раз.
Например, к нам приходят гости, и нам хочется всех запомнить. Однако повторные посещения не должны приводить к дублированию результатов. Посетитель должен быть «засчитан» только один раз.
Set
как раз подходит для этого:
пусть set = новый Set(); пусть Джон = {имя: "Джон"}; let Пит = {имя: "Пит"}; let Мэри = {имя: "Мэри"}; // посещения, некоторые пользователи приходят несколько раз set.add(Джон); set.add(Пит); set.add(Мэри); set.add(Джон); set.add(Мэри); // set сохраняет только уникальные значения оповещение(set.size); // 3 for (пусть пользователь набора) { оповещение(имя_пользователя); // Джон (затем Пит и Мэри) }
Альтернативой Set
может быть массив пользователей и код для проверки дубликатов при каждой вставке с помощью arr.find. Но производительность была бы намного хуже, потому что этот метод проходит по всему массиву, проверяя каждый элемент. Set
гораздо лучше внутренне оптимизирован для проверок уникальности.
Мы можем перебирать набор либо с помощью for..of
, либо с помощью forEach
:
let set = new Set(["апельсины", "яблоки", "бананы"]); for (пусть заданное значение) alert(value); // то же самое с forEach: set.forEach((value, valueAgain, set) => { предупреждение (значение); });
Обратите внимание на забавную вещь. Функция обратного вызова, переданная в forEach
имеет 3 аргумента: value
, затем то же значение valueAgain
и затем целевой объект. Действительно, одно и то же значение появляется в аргументах дважды.
Это сделано для совместимости с Map
, где обратный вызов, передаваемый forEach
имеет три аргумента. Выглядит немного странно, конечно. Но в некоторых случаях это может помочь легко заменить Map
на Set
, и наоборот.
Также поддерживаются те же методы, которые Map
имеет для итераторов:
set.keys()
– возвращает итерируемый объект для значений,
set.values()
— то же, что и set.keys()
, для совместимости с Map
,
set.entries()
— возвращает итерируемый объект для записей [value, value]
, существует для совместимости с Map
.
Map
– это набор ключевых значений.
Методы и свойства:
new Map([iterable])
— создает карту с необязательной iterable
(например, массивом) пар [key,value]
для инициализации.
map.set(key, value)
— сохраняет значение по ключу, возвращает саму карту.
map.get(key)
– возвращает значение по ключу, undefined
, если key
не существует в карте.
map.has(key)
– возвращает true
если key
существует, в противном случае — false
.
map.delete(key)
— удаляет элемент по ключу, возвращает true
если key
существовал на момент вызова, в противном случае — false
.
map.clear()
– удаляет все с карты.
map.size
– возвращает текущее количество элементов.
Отличия от обычного Object
:
Ключами могут быть любые ключи, предметы.
Дополнительные удобные методы — свойство size
.
Set
– это набор уникальных значений.
Методы и свойства:
new Set([iterable])
– создает набор с необязательной iterable
(например, массивом) значений для инициализации.
set.add(value)
– добавляет значение (ничего не делает, если value
существует), возвращает сам набор.
set.delete(value)
– удаляет значение, возвращает true
, если value
существовало на момент вызова, в противном случае — false
.
set.has(value)
– возвращает true
, если значение существует в наборе, в противном случае — false
.
set.clear()
– удаляет все из набора.
set.size
– количество элементов.
Итерация над Map
и Set
всегда происходит в порядке вставки, поэтому мы не можем сказать, что эти коллекции неупорядочены, но мы не можем переупорядочивать элементы или напрямую получать элемент по его номеру.
важность: 5
Пусть arr
будет массивом.
Создайте функцию unique(arr)
, которая должна возвращать массив с уникальными элементами arr
.
Например:
функция unique(arr) { /* ваш код */ } letvalues = ["Заяц", "Кришна", "Заяц", "Кришна", «Кришна», «Кришна», «Заяц», «Заяц», «:-О» ]; Предупреждение(уникальный(значения)); // Заяц, Кришна, :-O
PS Здесь используются строки, но могут быть значениями любого типа.
PPS Используйте Set
для хранения уникальных значений.
Откройте песочницу с тестами.
функция unique(arr) { return Array.from(новый набор(arr)); }
Откройте решение с тестами в песочнице.
важность: 4
Анаграммы – это слова, в которых одинаковое количество одинаковых букв, но в разном порядке.
Например:
вздремнуть - кастрюля ухо - есть - эпоха мошенники - гектары - учителя
Напишите функцию aclean(arr)
, которая возвращает массив, очищенный от анаграмм.
Например:
let arr = ["сон", "учителя", "читеры", "ПАН", "ухо", "эпоха", "га"]; Предупреждение(ачист(арр)); // "сон,учителя,ухо" или "ПАН,читеры,эра"
От каждой группы анаграмм должно остаться только одно слово, неважно какое.
Откройте песочницу с тестами.
Чтобы найти все анаграммы, давайте разобьем каждое слово на буквы и отсортируем их. При сортировке по буквам все анаграммы одинаковы.
Например:
вздремнуть, кастрюля -> анп ухо, эпоха, являются -> aer мошенники, гектары, учителя -> aceehrst ...
Мы будем использовать варианты с сортировкой по буквам в качестве ключей карты, чтобы хранить только одно значение для каждого ключа:
функция aclean(arr) { пусть карта = новая карта(); for (пусть слово arr) { // разбиваем слово по буквам, сортируем их и соединяем обратно let sorted = word.toLowerCase().split('').sort().join(''); // (*) map.set(отсортировано, слово); } return Array.from(map.values()); } let arr = ["сон", "учителя", "читеры", "ПАН", "ухо", "эпоха", "га"]; Предупреждение(ачист(арр));
Сортировка букв осуществляется по цепочке вызовов в строке (*)
.
Для удобства разобьем его на несколько строк:
let sorted = слово // PAN .toLowerCase() // панорамирование .split('') // ['p','a','n'] .sort() // ['a','n','p'] .присоединиться(''); // анп
Два разных слова 'PAN'
и 'nap'
получают одну и ту же буквенную форму 'anp'
.
Следующая строка помещает слово на карту:
map.set(отсортировано, слово);
Если мы когда-нибудь снова встретим слово в той же форме, отсортированной по буквам, то оно перезапишет предыдущее значение с тем же ключом на карте. Таким образом, у нас всегда будет максимум одно слово на буквенную форму.
В конце Array.from(map.values())
принимает итерацию по значениям карты (ключи в результате нам не нужны) и возвращает их массив.
Здесь мы также могли бы использовать простой объект вместо Map
, поскольку ключи — это строки.
Вот как может выглядеть решение:
функция aclean(arr) { пусть объект = {}; for (пусть я = 0; я <arr.length; i++) { let sorted = arr[i].toLowerCase().split("").sort().join(""); объект [отсортировано] = arr [я]; } вернуть Object.values(obj); } let arr = ["сон", "учителя", "читеры", "ПАН", "ухо", "эпоха", "га"]; Предупреждение(ачист(арр));
Откройте решение с тестами в песочнице.
важность: 5
Мы хотели бы получить массив map.keys()
в переменной, а затем применить к нему методы, специфичные для массива, например .push
.
Но это не работает:
пусть карта = новая карта(); map.set("имя", "Джон"); пусть ключи = map.keys(); // Ошибка:keys.push не является функцией keys.push("ещё");
Почему? Как мы можем исправить код, чтобы keys.push
работали?
Это связано с тем, что map.keys()
возвращает итерируемый объект, а не массив.
Мы можем преобразовать его в массив, используя Array.from
:
пусть карта = новая карта(); map.set("имя", "Джон"); пусть ключи = Array.from(map.keys()); keys.push("ещё"); оповещение (ключи); // имя, подробнее