Функции map, flatMap и compactMap в программировании на Swift

В арсенале Swift есть полезные функции для преобразования коллекций без использования циклов, которые позволяют использовать минимум кода. В этой статье мы обсудим функции map(_:), flatMap(_:) и compactMap(_:).

Данные функции происходят из области функционального программирования. Они называются функциями высшего порядка, потому что они принимают другие функции в качестве входных данных.

Вы можете использовать функции высшего порядка для любой коллекции — массива, словаря, множества.

Когда вы разрабатываете приложения для iOS, вы обычно используете объектно-ориентированное программирование. Функциональное программирование отличается: оно имеет дело только с функциями. Нет переменных, нет состояния, нет циклов — только функции.

Язык программирования Swift идеально подходит для смешивания функционального программирования с нефункциональными подходом, такими как ООП. Вам не нужно писать строго функциональный код, а использование концепций из функционального программирования поможет вам сделать ваш код намного лучше.

Функция map

Эта функция выполняет последовательное преобразование каждого элемента в коллекции и возвращает коллекцию элементов, к которым была применено преобразование.

let numbers = [2, 3, 4, 5]
let result = numbers.map({ $0 * $0 })
 
print(result)
// [4, 9, 16, 25]

Сначала мы создаем массив numbers с несколькими значениями типа Int. Затем вызывается функция map(_:) и ее результат присваивается константе result.

Функция имеет один параметр — замыкание, которыое возвращает результат $0 * $0.

$0 соответствует первому параметру замыкания или каждому числу в нашей коллекции.

Мы умножаем каждое число на само себя, тем самым рассчитывая его квадрат. Таким образом мы трансформируем один массив в другой.

Аналогичное преобразование массива можно также выполнить с помощью цикла for:

let numbers = [2, 3, 4, 5]
var result = [Int]()
 
for number in numbers {
    result += [number * number]
}
 
print(result)
// [4, 9, 16, 25]

То есть с помощью функции map(_:) входной массив чисел преобразуется в другой массив чисел в последовательности:

2 => 2 * 2 => 4
3 => 3 * 3 => 9
4 => 4 * 4 => 16
5 => 5 * 5 => 25

Разберем еще один пример.

let celsius = [-5.0, 10.0, 21.0, 33.0, 50.0]
let fahrenheit = celsius.map { $0 * (9/5) + 32 }
print(fahrenheit)
// [23.0, 50.0, 69.80000000000001, 91.4, 122.0]

Данный результат так же может быть получен с помощью цикла for:

let celsius = [-5.0, 10.0, 21.0, 33.0, 50.0]
var fahrenheit: [Double] = []
 
for value in celsius {
    fahrenheit += [value * (9/5) + 32]
}
 
print(fahrenheit)
// [23.0, 50.0, 69.8, 91.4, 122.0]

Вы можете записать весь код в одну строку:

[-5.0, 10.0, 21.0, 33.0, 50.0].map { $0 * (9/5) + 32 }

Функция map(_:) преобразует один массив в другой, применяя замыкание к каждому элементу в массиве. Замыкание принимает входное значение в градусах Цельсия и возвращает значение в градусах Фаренгейта. Полученный массив построен из преобразованных значений — $0 * (9/5) + 32.

Давайте внимательнее посмотрим на замыкание. Если вы раньше работали с замыканиями, вы должны быть знакомы с более коротким способ использовать замыкание, исключив большую часть его синтаксиса. Вот менее краткая альтернатива:

let celsius = [-5.0, 10.0, 21.0, 33.0, 50.0]
 
let fahrenheit = celsius.map({ (value: Double) -> Double in
    return value * (9/5) + 32
})
 
print(fahrenheit)

Первая часть замыкания, начиная с {, указывает, что это замыкание имеет один параметр типа Double, и замыкание также возвращает значение типа Double. Тело замыкания, начиная с return, возвращает результат вычисления градусов по Цельсию в градусах Фаренгейта.

Если вы сравните синтаксис сокращенного замыкания с приведенным выше расширенным кодом, вы увидите, что:

  • Скобки после функции () опущены, потому что вы можете опустить их, когда последний параметр вызова функции является замыканием.
  • () -> in — эта часть также может быть опущена, поскольку Swift может сделать вывод, что вы используете один параметр типа Double в качестве входных данных, и ожидается, что он вернет также тип Double. Теперь, когда вы опустили переменную, вы можете использовать сокращение $0 для передаваемого параметра.
  • return также может быть опущен.

Функция flatMap

Функция flatMap(_:) в отличие от map(_:) всегда возвращает только одномерный массив:

let numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
let result = numbers.flatMap({ $0 })
 
print(result)
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

Приведенный выше код состоит из 3 вложенных массивов целых чисел, каждый из которых содержит по 3 числа.

$0 возвращает первый аргумент замыкания, то есть отдельно вложенные массивы. Никаких преобразований при этом не происходит. Возвращемый результат уже не содержит вложенных массивов.

Давайте посмотрим на другой пример. Представьте, что вы работаете с 4 группами жирафов и хотите создать одну группу жирафов, которая выше определенной высоты:

let giraffes = [[5, 6, 9], [11, 2, 13, 20], [1, 13, 7, 8, 2]]
let tallest = giraffes.flatMap({ $0.filter({ $0 > 10 }) })
 
print(tallest)
// [11, 13, 20, 13]

В приведенном выше коде функция filter(_:) вызывается для каждого вложенного массива. Полученные массивы сведены в один одномерный массив и присвоены константе tallest.

Если бы в данном коде мы бы использовали функцию map(_:) вместо flatMap(_:) мы бы получили несколько массивов

let giraffes = [[5, 6, 9], [11, 2, 13, 20], [1, 13, 7, 8, 2]]
let tallest = giraffes.map({ $0.filter({ $0 > 10 }) })
 
print(tallest)
// [[], [11, 13, 20], [13]]

Важно отметить, что функция flatMap(_:) сначала вызывает отдельные элементы массива, а затем сводит их в один массив. Вот почему следующий код не будет работать:

let numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
let result = numbers.flatMap({ $0 * 2 })

В приведенном выше примере $0 ссылается на отдельные массивы внутри массива numbers. Умножить массив на два невозможно, поэтому данный код не работает.

Функция compactMap

Функция compactMap(_:) удаляет значения nil из массива. Она очень полезна при работе с опционалами.

let numbers = ["5", "42", "nine", "100", "Bob"]
let result = numbers.compactMap({ Int($0) })
 
print(result)
// [5, 42, 100]

Наиболее важной частью кода является Int($0). Мы берем каждую строку из массива и пытаемся ее преобразовать в целое число. Инициализатор Int() провалющивайся (failable). То есть он возвращает опциональные значения типа Int?. В результате тип возвращаемого преобразования — массив опциональных целых чисел [Int?].

[Optional(5), Optional(42), nil, Optional(100), nil]

Функция compactMap(_:) автоматически удаляет nil элементы из возвращаемого массива. Таким образом, тип возвращаемого значения больше не является опциональным.

[5, 42, 100]

Приведенный код имеет тип [Int]. Если бы мы использовали функцию map(_:), возвращаемый тип был бы [Int?] и нам бы понадобился дополнительный шаг, чтобы извлечь опциональные значения из массива.

Чем могут быть полезны данные функции?

Давайте кратко рассмотрим описанные функции высшего порядка и их цели:

  • Функция map(_:) применяет замыкание к каждому элементу коллекции и тем самым ее преобразует в новую коллекцию.
  • Функция flatMap(_:) делает то же самое, но при этом возвращает одномерный массив.
  • Функция flatMap(_:) делает то же самое, но при этом удаляет все значения nil из коллекции.

Чем данные функции могут быть полезны?

  • Данные функции позволяют сделать наш код более кратким по сравнению с циклами, потому что вам больше не нужны временные переменные и многострочный код цикла for.
  • Вы можете написать вызов функции в одну строку, что делает ваш код более читабельным.
  • Подобные функции могут быть объединены в одну цепочку, поэтому вы можете использовать несколько последовательных преобразований к одной коллекции.

В целом, функции высшего порядка полезны, потому что они позволяют применить всего одну функцию к последовательности и получить нужный результат.

К примеру, функция flatMap(_:) предоставляет наиболее практичный вариант для работы с входными значениями коллекции, которые сгруппированы или вложены друг в друга, но при этом требуемое выходное значение должно быть одномерным.

Например, в музыкальном приложении у вас могут быть 3 типа массивов: песни, исполнители и плейлисты. При этом каждый из данных типов имеет свойство isFavorite. Мы можем объединить все три массива в один массив, вызвать функцию flatMap(_:) и выбирать песни, исполнители или плейлисты, для которых значение isFavorite равно true. В итоге мы получим одномерный массив с нужными значениями.

Представьте, что вы создаете приложение для социальных сетей. Вам нужно создать ленту новостей для каждого пользователя. Вы используете 3 запроса, чтобы выбрать идентификаторы сообщений для пользователя. Например, из публикаций подписчиков, рекламных объявлений и популярных тем.

  • Вы можете использовать функцию map(_:) чтобы получить посты через значения ID.
  • Вы можете использовать функцию flatMap(_:), чтобы объединить три группы постов в одну коллекцию.
  • Функция compactMap(_:) позволит вам отфильтровать полученные посты.
Читайте также:
Комментарии (1)
  1. Опечатка
    Функция map(_:) применяет замыкание к каждому элементу коллекции и тем самым ее преобразует в новую коллекцию.
    Функция flatMap(_:) делает то же самое, но при этом возвращает одномерный массив.
    Функция flatMap(_:) делает то же самое, но при этом удаляет все значения nil из коллекции.

Добавить комментарий

Ваш адрес email не будет опубликован.