Нужно понять, сколько элементов реально проверит поиск первого подходящего значения. Что будет напечатано и на каком элементе завершится обход?
let numbers = [1, 2, 3, 4]
let result = numbers.first { number in
print("проверка \(number)")
return number % 2 == 0
}
print(result ?? -1)
Будет напечатано:
first(where:) последовательно проверяет элементы и немедленно прекращает обход после первого значения, для которого замыкание вернуло true. Элементы 3 и 4 не проверяются.
Операции вроде first(where:), contains(where:) и allSatisfy предназначены для поиска или проверки условия без обязательного полного обхода последовательности. Такой подход позволяет не выполнять лишнюю работу, особенно когда подходящий элемент находится в начале или середине источника данных.
Это отличается от построения промежуточной коллекции через filter: filter проверяет все элементы, потому что должен собрать все совпадения. У короткозамыкающих операций результат можно получить раньше.
Если разработчик ошибочно предполагает, что first(where:) проверит весь массив, он может неверно оценить количество побочных эффектов и стоимость операции. В данном примере вызов print внутри предиката показывает фактический порядок и число проверок.
Важно не помещать в такое замыкание действия, которые должны выполняться для каждого элемента. Короткозамыкающая операция имеет право завершить обход сразу после получения достаточной информации.
first(where:) получает элементы последовательности по одному и передаёт каждый элемент в замыкание-предикат. Для 1 условие ложно, поэтому обход продолжается. Для 2 условие истинно, значение сохраняется как результат, а дальнейший вызов предиката не выполняется.
Операция является нетерминальным преобразованием? Нет: она терминирующая, потому что возвращает конкретный результат, а не новую последовательность. Она выполняется сразу при вызове, независимо от того, является исходная коллекция массивом или другой Sequence.
Если совпадение не найдено, first(where:) возвращает nil. Поэтому результат обычно имеет optional-тип, а обработка выполняется через if let, guard let, оператор ?? или другой подход к optional.
Короткое замыкание влияет на сложность: в лучшем случае будет одна проверка, в худшем — столько проверок, сколько элементов в последовательности. Для обычного массива это обычно описывают как O(k), где k — позиция первого совпадения, а в худшем случае — O(n).
В приложении нужно найти первую доступную конфигурацию среди нескольких вариантов. Вариант filter { ... }.first сначала проверит все элементы и создаст промежуточный массив, а затем возьмёт его первый элемент. Плюс такого решения — удобство, но оно выполняет лишнюю работу и требует дополнительной памяти.
Вариант first(where:) не создаёт промежуточную коллекцию и останавливается на первом совпадении. Поэтому он выбранным решением будет предпочтительнее: меньше проверок, меньше аллокаций и отсутствие ненужного хранения всех совпадений.
Если требуется проверить только наличие элемента, ещё яснее выразить намерение через contains(where:). Если же нужны все совпадения, короткое замыкание применять нельзя — тогда подходит filter.
first(where:) вызывает предикат хотя бы один раз?Нет. Для пустой последовательности замыкание не будет вызвано ни разу, а результатом станет nil. Поэтому нельзя помещать в предикат обязательную инициализацию или рассчитывать на побочный эффект без проверки наличия элементов.
first(where:)?Да, как только предикат вернёт true, операция запрашивать следующие элементы не будет. Для ленивого или внешнего источника это может быть существенно: чтение файла, генерация значений или сетевой источник могут не дойти до оставшихся элементов. Однако уже выполненные побочные эффекты отменены не будут.
first(where:) отличается от filter { ... }.first не только по памяти?filter гарантированно обрабатывает всю исходную последовательность и собирает все совпадения, после чего first выбирает первый элемент результата. first(where:) останавливается при первом совпадении, поэтому может быть значительно быстрее и не выполняет предикат для последующих элементов. Кроме того, при наличии побочных эффектов порядок и число вызовов замыкания у этих вариантов различаются.