Следующая статья поможет вам: FlatMap и CompactMap, объясненные на Swift
Вернуться в блог
В Swift есть множество функций, полезных для преобразования коллекций и последовательностей. В этом руководстве по разработке приложений мы обсудим карты(_:), FlatMap(_:) и CompactMap(_:).
Вот на чем мы сосредоточимся:
- Как map(_:) преобразует коллекцию или последовательность, применяя к ней замыкание
- Как FlatMap(_:) может сгладить входной массив после вызова map(_:)
- Как CompactMap(_:) удаляет ноль из входного массива
В предыдущем уроке мы обсуждали, как можно использовать фильтр(_:) и сокращение(_:). Эти функции высшего порядка очень полезны для преобразования коллекций в сжатой и содержательной форме.
В качестве быстрого напоминания о функциях высшего порядка в Swift нашей отправной точкой является функция map(_:). Эта функция применяет преобразование к каждому элементу последовательности, например массива или словаря.
Вот пример:
пусть числа = [2, 3, 4, 5]
пусть результат = Numbers.map({ $0 * $0 })
печать (результат)
Давайте разберем это:
Сначала мы создаем массив чисел с несколькими целочисленными значениями. Затем функция map(_:) вызывается для чисел, и ее результат присваивается результату.
Функция map(_:) имеет один параметр — замыкание, которое возвращает результат $0 * $0. $0 соответствует первому параметру замыкания, т.е. числу преобразуемых чисел.
Мы вычисляем квадрат каждого числа в числах. По сути, операция $0 * $0 вызывается для каждого числа в числах, и результирующий массив присваивается результату. Вы преобразуете – или «сопоставляете» – один массив в другой.
Преобразование массива с помощью карты(_:) похоже на использование цикла for, но гораздо более кратко. Так:
пусть числа = [2, 3, 4, 5]
вар результат = [Int]()
для числа в цифрах {
результат += [number * number]
}
печать (результат)
Вот еще один способ взглянуть на это. С помощью map(_:) входной массив чисел преобразуется в другой массив чисел. Так:
2 => 2 * 2 => 4
3 => 3 * 3 => 9
4 => 4 * 4 => 16
5 => 5 * 5 => 25
Такие функции, как map(_:), называются функциями высшего порядка, поскольку они принимают на вход функцию, а не обычные значения. Функции высшего порядка также могут выводить функции, что полезно для парадигмы программирования, называемой функциональным программированием.
Технически вы можете вызывать функции более высокого порядка, такие как map(_:), в любой последовательности. Сюда входят такие коллекции, как массивы, словари и множества, диапазоны, такие как 1…100, и так называемые итераторы. По сути, все, что выглядит как «список» значений.
В конце этого руководства мы обсудим, почему функции высшего порядка полезны. Давайте сначала перейдем к изучению FlatMap(_:) и CompactMap(_:).
Функция FlatMap(_:) аналогична функции map(_:) за исключением того, что она «сглаживает» результирующий массив. Вот пример:
пусть числа = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
пусть результат = Numbers.FlatMap({ $0 })
печать (результат)
Приведенный выше код начинается с вложенного массива целых чисел. Массив чисел состоит из массива из трех массивов, каждый из которых содержит по три числа.
Замыкание { $0 } просто возвращает первый аргумент замыкания, то есть отдельные вложенные массивы. Никакой трансформации или операции не происходит. Однако когда вы вызываете FlatMap(_:) для массива чисел, вместо Map(_:) вы получаете сглаженный массив отдельных чисел. В отличие от чисел входного массива, результирующий массив не содержит вложенных массивов!
Давайте посмотрим на другой пример. Представьте, что вы работаете с четырьмя группами жирафов и хотите создать одну группу жирафов ростом выше определенного роста. Вот как это сделать:
пусть жирафы = [[5, 6, 9], [11, 2, 13, 20], [1, 13, 7, 8, 2]]
пусть самый высокий = giraffes.flatMap({ $0.filter({ $0 > 10 }) })
распечатать (самый высокий)
Видите, как жирафы содержат массив массивов? В приведенном выше коде функция filter(_:) вызывается для каждого вложенного массива внутри жирафов. Нам нужны только целые числа (жирафы!), превышающие 10. Полученные массивы сводятся в один «плоский» массив и присваиваются самым высоким.
Представьте, что произошло бы, если бы мы использовали Map(_:) вместо FlatMap(_:). Полученный массив не будет сглажен. Вместо этого это было бы следующее:
[[], [11, 13, 20], [13]]
Важно отметить, что функция FlatMap(_:) сначала вызывает карту(_:) для элементов массива, а затем выравнивает их. Вот почему что-то вроде следующего не работает:
пусть числа = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
пусть результат = Numbers.FlatMap({ $0 * 2 })
В приведенном выше коде $0 относится к массивам внутри чисел. Умножить массив на два невозможно, поэтому этот код не работает.
Разумно думать о плоском отображении как о просмотре массива в одном измерении меньше. Вы начинаете с двумерного массива и заканчиваете одномерным массивом после FlatMap(_:).
А как насчет использования FlatMap(_:) с опциями? Давайте посмотрим на это дальше.
Название «компактная карта» основано на идее, что удаление нулевых элементов из массива делает массив более компактным. Аналогично, название «плоская карта» происходит от сглаживания массива. А «отображение» — это математическая концепция, согласно которой вы связываете значения одного набора с другим набором.
Функция CompactMap(_:) удаляет нулевые значения из входного массива. Это очень полезно при работе с опциями.
До Swift 4.1 функцию FlatMap(_:) (см. выше) также можно было использовать для фильтрации нулевых значений из сглаженных массивов. Начиная с Swift 4.1+, для этой цели теперь используется явный CompactMap(_:).
Вот пример:
пусть числа = [“5”, “42”, “nine”, “100”, “Bob”]
пусть результат = Numbers.compactMap({ Int($0) })
печать (результат)
Посмотрите, что происходит? Самая важная часть кода — Int($0). Это берет отдельную строку из чисел с $0 и пытается преобразовать ее в целое число с помощью инициализатора Int().
Этот инициализатор Int() не работает: он может возвращать ноль (необязательно), поэтому его тип возвращаемого значения — Int?. В результате тип возвращаемого значения преобразования отображения: [Int?] – массив необязательных целых чисел.
[Optional(5), Optional(42), nil, Optional(100), nil]
Функция CompactMap(_:) автоматически удаляет нулевые элементы из возвращаемого массива после вызова для него карты(_:). Таким образом, тип возвращаемого значения не является обязательным.
[5, 42, 100]
В приведенном выше коде тип результата: [Int]. Если бы вы использовали карту(_:), тип возвращаемого значения был бы [Int?]. И вам потребовался бы дополнительный шаг, чтобы развернуть значения из массива и работать с ними.
Прежде чем мы обсудим реальные варианты использования плоских и компактных карт, давайте кратко рассмотрим эти функции высшего порядка и их назначение.
- Функция map(_:) применяет замыкание к входной коллекции и возвращает преобразованную коллекцию.
- Функция FlatMap(_:) делает то же самое, а также сглаживает полученную коллекцию.
- Функция CompactMap(_:) делает то же самое, что и map(_:), а также удаляет ноль из результирующей коллекции.
Работая с map(_:), FlatMap(_:) и CompactMap(_:) в абстрактном виде, иногда трудно представить варианты их практического использования. Давайте обсудим, почему вы хотите их использовать.
Использование таких функций, как map(_:), для применения преобразований к последовательностям имеет несколько преимуществ:
- Это более лаконично, чем использование цикла for, поскольку вам не нужны временные переменные и многострочный блок for in { }.
- Обычно вы можете написать вызов map(_:) в одной строке, что (обычно) делает ваш код более читабельным.
- Такие функции, как map(_:), можно объединять в цепочки, поэтому вы можете применять несколько преобразований к последовательности одно за другим.
В целом функции высшего порядка полезны, поскольку позволяют применять функцию к последовательности значений. Вместо процедурного кодирования преобразования вы можете просто применить функцию и получить результат обратно.
Наиболее практичным вариантом использования FlatMap(_:) является работа с входными значениями, которые сгруппированы или вложены, но выходное значение, которое вам нужно, должно быть одномерным.
Например, в музыкальном приложении вы можете иметь три массива: песни, исполнители и плейлисты. Вы объединяете их в один массив, вызываете для него FlatMap(_:), выбираете песни, исполнителей и плейлисты, для которых isFavorite имеет значение true, и в конечном итоге получаете один плоский список элементов, которые были добавлены в избранное.
Практический вариант использования CompactMap(_:) — работа с преобразованием, которое может возвращать ноль. Вы сэкономите несколько тривиальных шагов, позволив CompactMap(_:) немедленно отфильтровать нулевые значения. Дополнительным преимуществом является необязательный тип возвращаемого значения CompactMap(_:); Для сравнения, функция filter(_:) вернула бы необязательное значение, если бы вы отфильтровали ноль.
Вы можете комбинировать FlatMap(_:) и CompactMap(_:) и даже фильтровать(_:) или уменьшать(_:_:). Представьте, что вы создаете приложение для социальных сетей. Вы хотите создать шкалу публикаций для пользователя. Вы используете 3 запроса для выбора идентификаторов сообщений для пользователя, например, из сообщений подписчиков, рекламных объявлений и популярных тем.
- Вы можете использовать карту(_:) для расширения этих идентификаторов в реальные объекты Post.
- Вы можете использовать FlatMap(_:), чтобы объединить три группы в одну коллекцию.
- Вы можете использовать CompactMap(_:) для удаления сообщений, которые невозможно расширить.
Дальнейшее чтение
Стоит узнать о картах (_:), FlatMap(_:) и CompactMap(_:), поскольку они делают ваш код более кратким и читабельным. Вы можете добавить больше функционального программирования в код вашего приложения. Как только вы к ним привыкнете, вы не сможете поверить, что можете выполнять свою работу без них.
Особенно стоит отметить различия между Map(_:), FlatMap(_:) и CompactMap(_:). Первый применяет преобразование к последовательности, второй выравнивает результирующий массив, а третий удаляет нулевые значения перед возвратом результата. Потрясающий!