Прежде чем начать что-либо менять, запустите Instruments и определите, где находятся ваши узкие места. Очень легко преследовать неправильные вещи.
Я очень подозрительно отношусь к этому числу 60-х годов. Это огромное количество времени и предполагает, что вы действительно делаете эту фильтрацию неоднократно. Могу поспорить, вы делаете это один раз в видимый ряд или что-то в этом роде. Это объясняет, почему во второй раз это происходит намного быстрее. 300к это много, но на самом деле это не так уж много. Компьютеры работают очень быстро, и минута - это очень много времени.
Тем не менее, существуют некоторые очевидные проблемы с вашим существующим фильтром. Он пересчитывает prefix.lowercased()
300k раз, что не нужно. Вы можете извлечь это:
let lowerPrefix = prefix.lowercased()
filteredBooks = books.filter { $0.title.lowercased().hasPrefix(lowerPrefix) }
Точно так же вы пересчитываете все title.lowercased()
для каждого поиска, и вам почти никогда не нужно все это. Вы можете кэшировать версии в нижнем регистре, но вы также можете просто прописать в нижнем регистре то, что вам нужно:
let lowerPrefix = prefix.lowercased()
let prefixCount = prefix.count // This probably isn't actually worth caching
filteredBooks = books.filter { $0.title.prefix(prefixCount).lowercased() == lowerPrefix }
Я сомневаюсь, что вы получите много пользы таким образом, но это то, что нужно изучить, прежде чем исследовать романструктуры данных.
Тем не менее, если единственный вид поиска, который вам нужен, это поиск по префиксу, Trie определенно предназначен именно для этой проблемы. И да, двоичный поиск также стоит рассмотреть, можете ли вы сохранить свой список в порядке заголовка, и поиск префиксов - единственное, что вас волнует.
Хотя это не поможет при первом поиске, имейте в виду, чтоВаш второй поиск часто может быть намного быстрее путем кэширования последних поисков. В частности, если вы уже искали «a», то вы знаете, что «ap» будет подмножеством этого, поэтому вам следует использовать этот факт. Точно так же подобные поиски очень часто повторяются, когда пользователи делают опечатки и возврат. Таким образом, сохранение некоторых недавних результатов может быть большим выигрышем за счет памяти.
При таких масштабах распределение памяти и копирование могут быть проблемой. Тип вашей книги имеет порядок 56 байтов:
MemoryLayout.stride(ofValue: Book()) // 56
(размер такой же, но шаг немного более значим, когда вы думаете о размещении их в массиве; он включает любые отступы между элементамиВ этом случае отступ равен 0. Но если вы добавите свойство Bool, вы увидите разницу.)
Содержимое строк не нужно копировать (если нет мутации), поэтомуэто не имеет значения, как долго строки. Но метаданные должны быть скопированы, и это складывается.
Таким образом, полная копия этого массива порядка 16 МБ данных, которые необходимо скопировать. Самое большое подмножество, которое вы ожидаете, будет 10-15% (10% слов начинаются с самой распространенной буквы, s , в английском языке, но названия могут немного исказить это). Это по-прежнему порядка мегабайта копирования на фильтр.
Вы можете улучшить это, работая исключительно с индексами, а не с полными элементами. К сожалению, в stdlib нет хороших инструментов для этого, но их не так сложно написать.
extension Collection {
func indices(where predicate: (Element) -> Bool) -> [Index] {
indices.filter { predicate(self[$0]) }
}
}
Вместо того, чтобы копировать 56 байтов, это копирует 8 байтов на результат, что может значительно уменьшить отток памяти.
Вы также можете реализовать это как IndexSet;Я не уверен, с кем будет работать быстрее.