JavaScript / 39 минут
Map и Set
Убирайте дубликаты и разделяйте код на модули с явными зависимостями.
- Убирайте дубликаты и разделяйте код на модули с явными зависимостями.
- Применить тему в задаче «Map и Set» и объяснить граничные случаи.
Около 9 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Когда массива или обычного объекта недостаточно
Массив хорошо описывает порядок элементов, а объект — набор известных свойств сущности. Но есть задачи с другим главным вопросом: встречалось ли значение раньше или какое значение связано с произвольным ключом. Для них JavaScript предоставляет Set и Map. Выбор структуры данных помогает выразить правило напрямую, а не прятать его в повторяющихся ручных поисках.
Set хранит уникальные значения, Map связывает ключи со значениями. Обе структуры сохраняют порядок вставки для обхода. Они не являются автоматически сортированными коллекциями. Если нужен алфавитный порядок, его задают отдельно. Не смешивайте требование уникальности с требованием сортировки: в задаче «сохранить первое появление» сортировка как раз уничтожит нужный исходный порядок.
const tags = new Set(['js', 'ts', 'js']);
console.log([...tags]); // ['js', 'ts']
console.log(tags.has('js')); // trueОперации Set и смысл уникальности
add добавляет значение, has проверяет наличие, delete удаляет, size сообщает количество уникальных элементов. Повторное добавление существующего значения не создаёт дубликат и не меняет его исходное место в порядке вставки. После удаления и нового добавления значение оказывается на новой позиции. Эти детали полезны, если коллекция одновременно используется как множество и как журнал порядка.
Для объектов уникальность основана на идентичности, а не на совпадении полей. Два отдельных {id:1} останутся двумя элементами. Если предметная область считает записи с одинаковым id одной сущностью, нужно выделить id как ключ и явно выбрать, какую запись сохранять. Иначе внешне похожие результаты приведут к неожиданному числу элементов.
const first = { id: 1 };
const set = new Set([first, first, { id: 1 }]);
console.log(set.size); // 2
set.delete(first);
console.log(set.size); // 1Map и типы ключей
Ключом Map может быть не только строка, но и число, объект или другое значение. Обычные ключи свойств объекта ведут себя иначе: числовые имена обычно становятся строковыми. Map полезен, когда ключи поступают динамически и не должны смешиваться со свойствами прототипа. Для записи используют set, для чтения get, для проверки наличия has.
const scores = new Map();
scores.set('Анна', 10);
scores.set('Олег', 0);
console.log(scores.get('Олег')); // 0
console.log(scores.has('Новый')); // false
console.log(scores.get('Новый')); // undefinedЗначение undefined тоже можно сохранить в Map. Поэтому get(key) === undefined не всегда доказывает отсутствие ключа. Если различие важно, используйте has. Аналогично не проверяйте наличие баланса через if (map.get(key)): допустимый ноль окажется ложным. Правило отсутствия должно быть отделено от содержимого сохранённого значения.
Подсчёт частот и порядок обхода
Map удобно использовать как аккумулятор частот. Для каждого встреченного элемента берётся текущий счётчик, отсутствие заменяется нулём, затем сохраняется увеличенное число. Это пример соединения подходящей структуры данных с явным правилом обновления. В отличие от случайного глобального объекта, локальная Map создаётся заново при каждом расчёте и не смешивает результаты разных вызовов.
const counts = new Map();
for (const tag of ['js', 'ts', 'js']) {
counts.set(tag, (counts.get(tag) ?? 0) + 1);
}
for (const [tag, count] of counts) console.log(tag, count);Map и Set итерируемы, поэтому их можно обходить через for...of и преобразовать в массив. Для сериализации в JSON обычно сначала выбирают массив пар или другую явную структуру. JSON.stringify(new Map(...)) не создаёт автоматически ожидаемый объект со всеми записями. Формат хранения — отдельный контракт, который должен поддерживаться при восстановлении.
Что важно знать о стоимости операций
Использование Set вместо многократного includes часто делает алгоритм поиска дубликатов понятнее и эффективнее на больших входах. Однако не стоит обещать универсальное точное время для любой реализации и любого ключа. Важнее увидеть структуру алгоритма: повторный линейный поиск внутри внешнего цикла может приводить к квадратичному росту работы.
WeakMap и WeakSet нужны для других сценариев с объектными ключами и жизненным циклом памяти. Их не выбирают просто как «более быстрый Map». У них нет обычного перечисления всех записей. Слабая связь помогает не удерживать объект только из-за вспомогательных метаданных, но не заменяет явную очистку всех ресурсов приложения.
Уникальные строки в практике
Задание требует сохранить первые появления строк. Нужны две гарантии: каждое значение встречается в ответе не более одного раза, и относительный порядок первых появлений не меняется. Set естественно поддерживает обе для последовательного добавления элементов исходного массива. Пустой вход должен дать пустой массив, а повторение пустой строки должно обрабатываться так же, как повторение любого другого текста.
Проверьте пример ['ts','js','ts','node','js'] и ожидайте ['ts','js','node']. Нельзя сортировать ответ или приводить регистр без требования: 'JS' и 'js' — разные строки. После решения сформулируйте, как изменить алгоритм, если уникальность будет определяться регистронезависимым ключом, а сохранять потребуется исходное написание первого элемента.