Set vs Array: удаляем дубликаты в тегах без лишних костылей
-
Когда нужно обработать теги поста и оставить только уникальные значения, половина разработчиков тянется к привычным методам массивов, а потом удивляется, почему код работает медленнее, чем хотелось бы. На самом деле задача решается в одну строку, если знать нужный инструмент.
В этой статье разберёмся, почему Set - это не просто красивое решение, а настоящий выигрыш по производительности. Посмотрим, как он работает под капотом, какие ошибки подстерегают новичков, и когда Array всё же может быть полезнее.
Чем Set отличается от Array
Массив - это индексированная коллекция. Значения упорядочиваются по позиции: нулевой элемент, первый, второй. Каждый раз, когда нужно проверить, есть ли уже такой элемент, приходится либо использовать
indexOf(), либо перебирать весь массив циклом. Для массива с тысячей тегов это быстро становится боль.Set - это совсем другое животное. Это коллекция ключей, где каждое значение хранится только один раз. Когда вы пытаетесь добавить дубликат, Set просто его проигнорирует. Нет никаких проверок перед вставкой - это встроено в саму структуру данных.
- Индексированность: Array упорядочивает по индексам (0, 1, 2…), Set упорядочивает по ключам
- Уникальность: Array хранит всё, Set автоматически отбрасывает дубликаты
- Производительность проверки: Array требует линейного поиска, Set проверяет за O(1)
- Порядок элементов: Оба сохраняют порядок вставки, но только Array позволяет быстро обратиться по индексу
Как Set обрабатывает примитивы и объекты
Вот тут начинается самое интересное. Set работает с примитивами (строки, числа, boolean) по значению, а с объектами по ссылке.
Если у вас массив тегов вроде
['react', 'vue', 'react', 'angular']- Set справится за миллисекунду. Преобразовываем в Set и обратно в массив:const tags = ['react', 'vue', 'react', 'angular']; const uniqueTags = [...new Set(tags)]; // Результат: ['react', 'vue', 'angular']Но если вы решили хранить теги как объекты
{name: 'react'}, вот тут Set ловит косяк. Даже если два объекта выглядят идентично, это разные ссылки в памяти:const tagObjects = [{id: 1}, {id: 1}, {id: 1}]; const uniqueTagObjects = new Set(tagObjects); console.log(uniqueTagObjects.size); // 3, не 1!Почему так? Потому что Set сравнивает объекты по ссылке, а не по содержимому. Каждый
{id: 1}- это разные адреса в памяти. Если вам действительно нужно дедублицировать объекты, придётся писать свою логику или использовать Map с кастомным ключом.- Примитивы: Сравниваются по значению - два одинаковых числа считаются дубликатом
- Объекты: Сравниваются по ссылке - два объекта с одинаковыми свойствами это разные элементы
- Смешанные типы:
1(число) и'1'(строка) - разные значения, Set оба сохранит - Null и undefined: Сохраняются по одному экземпляру
Производительность: цифры говорят сами за себя
Если вы слышали, что Set быстрее - это не маркетинг, это факт. Тесты на реальных объёмах данных показывают: Set быстрее Array с filter в десятки раз, а reduce в сотню раз.
Когда вы вызываете
arr.indexOf()илиarr.includes()для проверки наличия элемента, браузер проходит весь массив с начала. Это O(n) операция. Повторяем проверку для каждого элемента - получаем O(n²). Set использует хеш-таблицу внутри и проверяет за O(1). Для массива из тысячи элементов разница измеряется в порядках величины.Сравните эти подходы:
Подход Код Скорость Set [...new Set(arr)]Базовая Filter + indexOf arr.filter((v, i) => arr.indexOf(v) === i)~10x медленнее Reduce arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], [])~100x медленнее Цикл с проверкой for+arr.includes()~20x медленнее // Измеряем самый быстрый способ const testArray = Array.from({length: 10000}, (_, i) => i % 100); console.time('Set approach'); const result1 = [...new Set(testArray)]; console.timeEnd('Set approach'); // ~1ms console.time('Filter approach'); const result2 = testArray.filter((v, i) => testArray.indexOf(v) === i); console.timeEnd('Filter approach'); // ~50msМетоды Set для работы с тегами
Kогда вы знаете, какие методы есть, работать с Set становится проще. Вот что вам нужно для обработки тегов:
set.add(value)- добавить тег в множество, дубликаты игнорируются автоматическиset.has(value)- проверить, есть ли тег уже в множестве (быстро, за O(1))set.delete(value)- удалить конкретный тегset.clear()- очистить всё множествоset.size- узнать количество уникальных тегов
Практический пример - обработка тегов поста:
const postTags = new Set(); // Добавляем теги пользователя postTags.add('javascript'); postTags.add('frontend'); postTags.add('javascript'); // Игнорируется // Проверяем наличие if (postTags.has('javascript')) { console.log('Тег уже есть'); } // Итерируем в порядке добавления postTags.forEach(tag => console.log(tag)); // javascript, frontend // Удаляем ненужное postTags.delete('frontend'); console.log(postTags.size); // 1Итерация всегда происходит в порядке вставки элементов. Это важно - если вы хотите сохранить порядок тегов, как их вводил пользователь, Set не подведёт.
Когда Array ещё может быть полезен
Не преувеличивайте, Set - не серебряная пуля. Есть сценарии, где Array всё ещё нужен.
Если вам нужно частно обращаться по индексу - Array выигрывает.
tagsэто O(1), а из Set элемент по номеру не достать. Также если вы работаете с очень маленькими коллекциями (пара десятков элементов), производительность Set не будет видна, а код может быть понятнее с привычным Array.Для древних браузеров (IE10 и ниже) Set просто не существует - придётся fallback’ить на Array. И если вы делаете какой-то специфический обход или трансформацию данных, Array методы могут быть удобнее.
// Array vs Set: когда Array проще const tags = ['react', 'vue', 'angular']; // Нужна трансформация каждого элемента const tagsByLength = tags.map(tag => tag.length); // Set это не может делать встроенно // Нужна фильтрация по условию const longTags = tags.filter(tag => tag.length > 5); // Set тоже не может // Нужен доступ по индексу const firstTag = tags; // Set заставит конвертировать в массивПрактический паттерн: обработка тегов поста
Большинство задач с дедупликацией тегов решаются одинаково. Вот универсальный подход, который работает в 90% случаев:
function getUniqueTags(rawTags) { // rawTags может быть грязным - с пробелами, дубликатами, пустыми строками return [ ...new Set( rawTags .map(tag => tag.trim().toLowerCase()) .filter(tag => tag.length > 0) ) ]; } const userInput = ['React', 'REACT', ' vue ', '', 'Angular', 'react']; const cleanTags = getUniqueTags(userInput); console.log(cleanTags); // ['react', 'vue', 'angular']Всё чётко: сначала нормализуем данные через map и filter, потом Set отрезает дубликаты, потом разворачиваем обратно в массив.
- Нормализация перед Set: Приводим к одному формату (lowercase, trim), убираем пустоту
- Set деплицирует: Одна операция, всё работает
- Разворот в массив: Если нужен массив дальше, используем spread или Array.from()
- Сохранение порядка: Set гарантирует порядок вставки
На чём не стоит зацикливаться
Есть несколько заблуждений, которые прилипают к Set. Первое - что Set это чудо-решение для всего подряд. Нет, это инструмент для конкретной задачи. Второе - что объекты в Set как-то магически дедублицируются. Нет, они сравниваются по ссылке, и это нормально.
Третье заблуждение - что нужны сложные полифиллы для старых браузеров. Set поддерживается везде, где нужно (IE11+, современные браузеры). Если вы ещё поддерживаете IE10, это вообще отдельная проблема, не только для Set.
И последнее - что производительность Set магична в каждом случае. Set быстрее именно на проверках уникальности и дедупликации больших объёмов. На маленьких массивах разница незаметна.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.