Перейти к содержимому
Теория · Массивы · урок 3 из 8

Поиск в массиве

Ряд шкафчиков можно перестроить, но чаще к нему подходят с вопросом. Что лежит в первом шкафчике и в последнем? Есть ли где-нибудь нужная вещь и под каким она номером? Чтобы ответить, ряд не трогают, а только заглядывают в шкафчики.

Часть ответов вы уже умеете получать по индексу. Последний элемент лежит по индексу count - 1. Но у пустого массива count равен 0, и запись scores[scores.count - 1] просит элемент с индексом -1. Такого шкафчика нет, и программа аварийно завершится с Index out of range.

Для таких вопросов у массива есть готовые средства. Они не завершают программу аварийно даже на пустом ряду:

SWIFT
let scores = [7, 9, 4, 9]
print(scores.first ?? 0, scores.last ?? 0)  // 7 9
print(scores.contains(9), scores.contains(5))  // true false
if let index = scores.firstIndex(of: 9) {
    print(index)  // 1
}
print(scores.max() ?? 0, scores.min() ?? 0)  // 9 4

Давайте разберём программу по строкам:

  1. first и last дают первый и последний элемент: 7 и 9. Ответ приходит в коробке-опционале, поэтому его открывают через ?? 0.
  2. contains(9) отвечает true: девятка в ряду есть. Пятёрки нет, и contains(5) даёт false.
  3. firstIndex(of: 9) находит первую девятку по индексу 1. Коробка не пуста, поэтому if let выполняет тело, и программа печатает 1.
  4. max() и min() находят наибольший и наименьший элемент: 9 и 4.

Массив scores объявлен через let, а программа всё равно компилируется: ни один из этих вопросов массив не меняет.

Четыре вопроса к массиву

first и last

first — первый элемент массива, last — последний. У строки они работают так же, только дают символ. Пишут их без скобок, как count.

contains

contains(9) проходит ряд и отвечает, есть ли в нём 9. Ответ — обычный Bool, и его можно сразу поставить в условие if. Если нужен не ответ «да или нет», а место элемента, берут firstIndex(of:).

firstIndex(of:)

firstIndex(of: 9) идёт по ряду с начала и возвращает индекс первой найденной девятки. Вторую девятку, по индексу 3, метод уже не ищет. Найденный индекс пригодится, чтобы заменить элемент через квадратные скобки или удалить его через remove(at:).

max() и min()

max() возвращает наибольший элемент, min() — наименьший. Их пишут с пустыми круглыми скобками, а first и last — без скобок.

Когда ответа нет

first, last, firstIndex(of:), max() и min() возвращают опционал. У пустого массива нет первого элемента, а искомого числа в ряду может не оказаться. Тогда вместо значения приходит пустая коробка — nil. Поэтому значение извлекают через ?? или if let, как в первой программе.

Здесь картинка со шкафчиками неточна. В раздевалке ряд стоит, даже когда все шкафчики пусты. А пустой массив — это ряд без единого шкафчика, и первого шкафчика в нём нет:

SWIFT
let empty = [Int]()
print(empty.first ?? 0, empty.max() ?? 0)  // 0 0
print(scores.firstIndex(of: 5) == nil)  // true
  1. У empty нет ни первого элемента, ни наибольшего, и оба ?? подставляют 0.
  2. Пятёрки в scores нет, поэтому firstIndex(of: 5) даёт nil, и сравнение с nil печатает true. С таким ответом if let из первой программы пропустил бы тело.

contains опционал не возвращает: на вопрос «есть ли?» ответ есть всегда, true или false.

На собеседовании могут спросить, чем scores.first отличается от scores[0]. Ответ: на пустом массиве scores[0] аварийно завершает программу, а first возвращает nil.

Частая ошибка

Разброс оценок посчитали как разность наибольшей и наименьшей:

SWIFT
let spread = scores.max() - scores.min() // ✗
TEXT
error: binary operator '-' cannot be applied to two 'Int?' operands

Программа не компилируется. max() и min() возвращают Int?, а вычесть одну коробку из другой нельзя: любая из них может оказаться пустой. Сначала откройте обе коробки, например одним if let:

SWIFT
if let best = scores.max(), let worst = scores.min() {
    print(best - worst)  // 5
}

Внутри тела best и worst — обычные числа 9 и 4, и разность 5 печатается без ошибок.

Главное

  • first и last дают элементы с краёв ряда, а max() и min() — наибольший и наименьший.
  • contains отвечает Bool, а firstIndex(of:) даёт индекс первого совпадения.
  • first, last, firstIndex(of:), max() и min() возвращают опционал; его извлекают через ?? или if let.
  • Эти вопросы не меняют массив, поэтому работают и с массивом через let.