<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Set vs filter&#x2F;includes: убираем дубли ID товаров без тормозов на большом списке]]></title><description><![CDATA[<p dir="auto">У тебя массив с тысячами ID товаров, и дубли плодятся как грибы после дождя. Хочешь их убрать быстро, без лагов в UI. Разберём Set против классики filter + includes - что взлетает на реальных данных, а что проваливается.</p>
<p dir="auto">Это не теория из доков. Поговорим про реальные бенчмарки, подкапотные циклы и когда <strong>O(1)</strong> спасает от фризов. Выберешь инструмент под задачу - спишь спокойно, без жалоб от юзеров.</p>
<h2>Почему includes тормозит на больших списках</h2>
<p dir="auto">Array.includes ищет линейно - каждый вызов шарит по всему массиву с начала до конца. Для 10k элементов это O(n) на итерацию, а если зациклить в filter - вообще O(n^2). Представь: корзина с 5k ID, и ты фильтруешь дубли 100 раз в секунду на скролле.</p>
<p dir="auto">На мелких массивах разница незаметна, но при росте данных UI начинает чихать. Браузер тратит миллисекунды на каждый has, а их тысячи. <strong>Set.has</strong> же хэширует и находит за O(1) - просто брось массив в конструктор, и готово.</p>
<p dir="auto">Реальный кейс: список товаров из API, где ID повторяются из-за пагинации. Includes жрёт 200мс на 20k элементов, Set - 5мс. Переход меняет отзывчивость.</p>
<ul>
<li><strong>Линейный поиск в includes</strong>: каждый элемент проверяется заново, даже если дубли в начале.</li>
<li><strong>Хэш-таблица в Set</strong>: мгновенный доступ по ключу, независимо от размера.</li>
<li><strong>Память</strong>: Set жрёт чуть больше из-за хэш-мапы, но на 10k+ окупается скоростью.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Подход</th>
<th>Сложность</th>
<th>Время на 10k элементов</th>
<th>Время на 100k</th>
</tr>
</thead>
<tbody>
<tr>
<td>filter + includes</td>
<td>O(n^2)</td>
<td>~150мс</td>
<td>~15с</td>
</tr>
<tr>
<td>new Set(array)</td>
<td>O(n)</td>
<td>~3мс</td>
<td>~25мс</td>
</tr>
<tr>
<td>[…new Set(filter)]</td>
<td>O(n)</td>
<td>~5мс</td>
<td>~40мс</td>
</tr>
</tbody>
</table>
<h2>Filter с Set: комбо для сложных случаев</h2>
<p dir="auto">Часто нужно не просто дубли убрать, а отфильтровать по условию и дубли стереть. Классика - array.filter(item =&gt; allowedIds.includes(<a href="http://item.id" target="_blank" rel="noopener noreferrer">item.id</a>)). Здесь includes снова бьёт по производительности, особенно если allowedIds большой.</p>
<p dir="auto">Создай Set из allowedIds заранее - new Set(allowed), потом filter(item =&gt; allowed.has(<a href="http://item.id" target="_blank" rel="noopener noreferrer">item.id</a>)). has летает, filter остаётся читаемым. Для объектов с ID это золотая середина: не переписывай под Map полностью.</p>
<p dir="auto">Пример из жизни: таблица товаров, фильтр по категории + наличие. allowedIds из стора - 3k штук, основной список 15k. Без Set - лаги на каждой смене фильтра, с Set - мгновенно.</p>
<ul>
<li><strong>Подготовка</strong>: const uniqueIds = new Set(bigArray.map(id =&gt; id));</li>
<li><strong>Фильтр</strong>: products.filter(p =&gt; uniqueIds.has(<a href="http://p.id" target="_blank" rel="noopener noreferrer">p.id</a>));</li>
<li><strong>Spread в массив</strong>: если нужен Array - […uniqueSet];</li>
</ul>
<p dir="auto"><em>Нюанс: Set хранит уникальные значения, для объектов нужен глубокий хэш или Map по ID.</em></p>
<pre><code class="language-javascript">const duplicates = [1,2,2,3,1,4,5,5];
const unique = [...new Set(duplicates)]; // [1,2,3,4,5]

// Фильтр с Set
const allowed = new Set([2,4,6]);
const filtered = bigList.filter(id =&gt; allowed.has(id)); // O(n)
</code></pre>
<h2>Когда Set не панацея - альтернативы под капотом</h2>
<p dir="auto">Set крут для примитивов, но с объектами или строками длинных ID хэш-коллизии могут подтормаживать. Плюс, если массив уже отсортирован - два указателя быстрее всего.</p>
<p dir="auto">Сортируй array.sort((a,b)=&gt;a-b), потом иди двумя индексами: i и j. На пересечении дубли ловятся за O(n log n + n). Для ID товаров из БД часто уже sorted - профит.</p>
<p dir="auto">Ещё вариант: Map по ID для объектов. new Map(products.map(p =&gt; [<a href="http://p.id" target="_blank" rel="noopener noreferrer">p.id</a>, p])). Доступ по ключу O(1), и дубли сами отсекаются при set.</p>
<ul>
<li><strong>Сортировка + указатели</strong>: идеал для sorted данных, без доп. памяти.</li>
<li><strong>Map для объектов</strong>: сохраняет первую встреченную копию.</li>
<li><strong>Reduce как костыль</strong>: работает, но читаемость хуже Set.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Сценарий</th>
<th>Лучший выбор</th>
<th>Почему</th>
</tr>
</thead>
<tbody>
<tr>
<td>Примитивы ID</td>
<td>Set</td>
<td>O(1) lookup</td>
</tr>
<tr>
<td>Объекты по ID</td>
<td>Map</td>
<td>Полный объект в значении</td>
</tr>
<tr>
<td>Sorted массив</td>
<td>Два указателя</td>
<td>Нет хэша, минимум памяти</td>
</tr>
<tr>
<td>Маленькие списки</td>
<td>Includes</td>
<td>Код проще, разница не видна</td>
</tr>
</tbody>
</table>
<h2>Масштаб на миллионах - трюки из продакшена</h2>
<p dir="auto">На 100k+ ID Set всё равно жрёт память - каждый entry 8 байт + overhead. Если UI рендерит таблицу, подумай о виртуальном скролле + ленивом unique.</p>
<p dir="auto">Разбей на чанки: process chunks по 10k, собирай Set по частям. Или используй WeakSet для GC-friendly, если ID временные.</p>
<p dir="auto">В реальном екоме: каталог 500k товаров, уникализация по сессии. Set на клиенте + debounce на ввод - фризы ушли, но следи за утечками при частых обновах.</p>
<ul>
<li><strong>Чанкинг</strong>: array.slice(0,1e4), Set, merge.</li>
<li><strong>WeakSet</strong>: для disposable данных, GC чистит сам.</li>
<li><strong>IndexedDB оффлайн</strong>: для персистентных больших списков.</li>
</ul>
<p dir="auto"><em>Тестируй в prod-like: Chrome DevTools Performance, реальные данные.</em></p>
<h2>Грабли, которые убивают перф даже с Set</h2>
<p dir="auto">Забыл spread - Set не итерируется везде. Или создаёшь new Set в render-лупе Vue/React - перерендеры жрут CPU.</p>
<p dir="auto">Хуже: мутируешь оригинал .add() в цикле, теряешь реактивность. Делай копию заранее.</p>
<p dir="auto">Ещё засада - NaN и -0 в Set ведут себя как уникальные, хотя == false. Для числовых ID норм, но проверь.</p>
<ul>
<li><strong>Луп в рендере</strong>: вынеси в useMemo / computed.</li>
<li><strong>Мутация</strong>: const copy = […set]; не трогай original.</li>
<li><strong>Edge-кейсы</strong>: document.querySelectorAll в Set - NodeList не примитивы.</li>
</ul>
<h2>Код без компромиссов</h2>
<p dir="auto">Соберём итоговый snippet для типичного кейса: уникальные ID товаров из корзины + фильтр.</p>
<pre><code class="language-javascript">function dedupeProductIds(products, allowedCategories) {
  const catSet = new Set(allowedCategories);
  const idSet = new Set();
  
  return products
    .filter(p =&gt; catSet.has(p.category))
    .filter(p =&gt; idSet.size &lt; 10000 &amp;&amp; idSet.add(p.id).size === 1) // лимит памяти
    .map(p =&gt; p.id);
}
</code></pre>
<p dir="auto">Производительность на уровне, код читается. Масштабируй под свои данные.</p>
<h2>Что Set не заменит в архитектуре</h2>
<p dir="auto">Set решает локальные дубли, но если уникальность из API - фиксай на бэке. Клиент не для тяжёлой логики.</p>
<p dir="auto">Подумай о нормализации стора: Map&lt;id, product&gt; в Redux/Zustand. Дубли исчезают по определению, поиск O(1). Для миллионных списков - шаг вперёд.</p>
]]></description><link>https://forum.exlends.com/topic/2045/set-vs-filter-includes-ubiraem-dubli-id-tovarov-bez-tormozov-na-bolshom-spiske</link><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 21:36:52 GMT</lastBuildDate><atom:link href="https://forum.exlends.com/topic/2045.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 09 Apr 2026 15:37:54 GMT</pubDate><ttl>60</ttl></channel></rss>