От квадратичной сложности к линейной

Приложение с эмодзи-клавиатурой, где пользователи могут видеть свои недавно использованные эмодзи в начале списка. Всё работает отлично, пока в emojis не оказывается 8 000+ элементов, а в recentEmojis — всего 8.

Казалось бы, безобидный код:

const matchedEmojis = useMemo(() => { return categories.flatMap((category) => category.emojis.filter((emoji) => recentEmojis.includes(emoji.unicode)) ); }, [categories, recentEmojis]);

…внезапно стал вызывать заметные торможения интерфейса.

На первый взгляд, код выглядит декларативно и элегантно. Но давайте разберем, что происходит под капотом: - filter пробегает по каждому эмодзи в категориях; - recentEmojis.includes() вызывается для каждого эмодзи; includes() сканирует массив recentEmojis каждый раз с нуля;

Цифры говорят сами за себя Всего эмодзи: 8 000+ RecentEmojis: 8 элементов Операций: 8 000 × 8 = 64 000 проверок на каждый рендер.

Но это еще не всё! Каждый раз, когда пользователь использует новый эмодзи, recentEmojis обновляется, и весь цикл повторяется снова.

Решение: индексация за O(1). Вместо того чтобы каждый раз искать эмодзи в массиве, мы создаем мапу, где ключем будет emoji.char, значение - ссылка на обьект. Это позволит находить эмодзи за константное время:

Анализ сложности Исходный код: Временная сложность: O(общее_число_эмодзи × recentEmojis.length). Для наших данных: 64 000 операций.

Оптимизированный код: Один раз: Шаг 1: Собираем все эмодзи в плоский массив — 8 000. Шаг 2: Создаем индекс мапу — ключ = код эмодзи, значение = объект эмодзи — 8 000.

Шаг 3: Удаляем дубликаты из recentEmojis - 8. Шаг 4: Быстрый поиск через Map — O(1) на каждый эмодзи - 8.

Итог: 16 операций на алгоритм вместо 64 000.

Классический пример того, как знание структур данных меняет подход к казалось бы простым задачам. Array.includes() — это удобно, но дорого. Map.get() — требует подготовки, но окупается при каждом обращении.

Бенчмарки Тестовые данные: 8 000 эмодзи, 8 в recentEmojis; Итераций: 100 пользовательских действий с выбором эмодзи; Исходный подход: 6 400 000 операций (в среднем 3726ms); Map-индекс подход: 16 772 операции (в среднем 47ms); Ускорение одного действия: в 79,27 раз 🚀

От квадратичной сложности к линейной | Сетка — социальная сеть от hh.ru От квадратичной сложности к линейной | Сетка — социальная сеть от hh.ru