Можно ли оптимизировать поиск с помощью сета
Что проверяют
Понимаете ли вы, откуда берётся выигрыш Set и чего он стоит. Обычно это часть задачи на кодинг вроде «найти дубликаты» или «пересечение двух массивов», а следом спрашивают, что нужно от своего типа, чтобы положить его в Set.
Как отвечать
Да, если поиск повторяется много раз. Set в Swift построен на хеш-таблице: contains в среднем работает за O(1), а Array.contains и firstIndex(of:) перебирают элементы за O(n). Проверка m элементов по массиву из n стоит O(n·m), через Set получается O(n + m).
Каркас ответа:
- построение:
Set(array)стоит O(n) по времени и отдельную память. Ради одного поиска это невыгодно, обычный перебор дешевле; - требование к типу: элемент должен быть
Hashable. Для своей структуры компилятор синтезирует соответствие, если все поляHashable. При ручной реализацииhash(into:)и==должны опираться на одни и те же поля; - порядок:
Setне хранит порядок и не допускает дублей. Порядок обхода к тому же меняется между запусками, потому что хеширование в Swift использует случайное зерно на процесс; - операции над множествами:
intersection,subtracting,union,isSubset(of:)решают половину задач без циклов; - альтернативы: если нужен ещё и индекс или значение, берите
Dictionary. Если данные уже отсортированы, бинарный поиск даст O(log n) без лишней памяти, хотя готового метода для него в стандартной библиотеке Swift нет.
Хорошо прозвучит оговорка про малые объёмы: на десятке элементов линейный поиск часто быстрее, потому что хеширование строки само стоит времени. В iOS-коде типичный случай это список с выбором: множество выбранных id вместо проверки по массиву в каждой ячейке.
Где спотыкаются
Называют O(1) без слов «в среднем». При плохой хеш-функции элементы попадают в одни и те же корзины, и поиск деградирует до O(n).
Делают Hashable класс, хеш которого зависит от изменяемых свойств, и меняют их у объекта, уже лежащего в Set. Элемент остаётся в старой корзине, и contains перестаёт его находить.
Строят Set из массива внутри цикла или прямо в cellForRowAt. Каждый вызов собирает множество заново, и оптимизация превращается в замедление.