Edited at

イメージで理解するSwiftの高階関数(filter, map, reduce, compactMap, flatMap)


はじめに

本記事ではSwiftの初心者〜中級者向けにSwift Standard Libraryフレームワークで提供されている主な高階関数について説明します。以下のようにイメージを合わせて書くことで、イメージが掴みやすいようにしました。

高階関数とは、関数を引数や戻り値にとる関数のことです。Swift Standard Libraryでは主に配列などで利用でき、for文で配列の各要素を処理するよりも宣言的なプロラミングが可能です。

例題)1〜10の数字から2の倍数だけ取得して、それぞれ3倍したものの合計

// for文を用いた例(命令型プログラミング)

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var result = 0
for element in array {
guard element % 2 == 0 else { continue }
result = result + (element * 3)
}

// 高階関数を用いた例(宣言型プログラミング)
// より例題をそのまま表現している
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let result = array
.filter { $0 % 2 == 0 }
.map { $0 * 3 }
.reduce(0) { $0 + $1 }

具体的な処理を関数(クロージャー)として指定できるため、高階関数自体が汎用的で利用しやすいものとなっています。


本記事で説明する高階関数

大きくはSequenceプロトコルに準拠した型(Arrayなど)で利用できるものとOptional型で利用できるものを説明します。

map、flatMapについては以下の通り、型によって2種類の関数があります。


Sequenceの高階関数


filter


イメージ

「リストの要素からなにかの条件に合うものだけを返す」


利用例

// 🥬と🐛のリストから、🐛を取り除いたリストを取得する

let array = ["🥬", "🐛", "🐛", "🥬", "🥬", "🐛"]

// このとき、$0はリスト内のそれぞれの要素を意味する
let result = array.filter { $0 != "🐛" }

print(result) // ["🥬", "🥬", "🥬"]


map


イメージ

「リストの全ての要素に何か変換をして返す」


利用例

// 🥬のリストの要素を🥗に変換したリストを取得する

let array = ["🥬", "🥬", "🥬"]

// ここで利用したreplacingOccurrences関数は文字列を置き換える関数
let result = array.map { $0.replacingOccurrences(of: "🥬", with: "🥗") }

print(result) // ["🥗", "🥗", "🥗"]


reduce


イメージ

「全ての要素に何か変換をして1つの結果を得る」


利用例

// 複数の🍓を含むリストから合計のいちごの数を数える

let array = ["🍓🍓", "🍓🍓🍓", "🍓"]

// このとき、$0は累積の結果、$1はリスト内のそれぞれの要素を意味する
// 第1引数の0は累積していく値の初期値
let result = array.reduce(0) { $0 + $1.count }

print(result) // 6


compactMap


イメージ

「全ての要素に何か変換をして、nilになったものを取り除いて返す」


利用例

// 🥬の場合は🥗に変換し、それ以外の場合はnilを返す

// nilとなる要素はcompactMapで取り除く
let array = ["🥬", "⚽", "🥬"]

let result = array.compactMap { $0 == "🥬" ? "🥗" : nil }
print(result) // ["🥗", "🥗"]

※補足

上記と同様の処理をmapを利用して行った場合は、nilを含むOptional<String>型の配列となります。

let array = ["🥬", "🥦", "🥬"]

let result = array.map { $0 == "🥬" ? "🥗" : nil }
print(result) // ["🥗", "🥗"] // [Optional("🥗"), nil, Optional("🥗")]"


flatMap


イメージ

「全ての要素に何か変換をして、その結果の要素が配列であればそれをフラットにして返す」


利用例

// 要素の🧟を[🧟, 🧟, 🧟]に変換し、その多重配列をフラットにしたものを返す

let array = ["🧟", "🧟"]
let result = array.flatMap { Array(repeating: $0, count: 3) }
print(result) // ["🧟", "🧟", "🧟", "🧟", "🧟", "🧟"]

※補足

上記と同様の処理をmapを利用して行った場合は、[[String]]型の配列となります。

let array = ["🧟", "🧟"]

let result = array.map { Array(repeating: $0, count: 3) }
print(result) // [["🧟", "🧟", "🧟"], ["🧟", "🧟", "🧟"]]


Optionalの高階関数


map


イメージ

「Optional型でラップされた型(中身)に対して何か変換を行う」


利用例

// Optional<String>型の変数をInt型に変換する

// init?(_ description: String)は失敗可能イニシャライザであるため、
// その結果はさらにOptional型となる
let number: String? = "01"
print(number.map { Int($0) })


flatMap


イメージ

「Optional型でラップされた型(中身)に対して何か変換を行い、その結果がOptional型であればアンラップする(多重のOptionalをフラットにする)」


利用例

// Optional<String>型の変数をInt型に変換する

// init?(_ description: String)は失敗可能イニシャライザであるため、
// その結果はさらにOptional型となるがflatMapによりアンラップされる
let number: String? = "01"
print(number.flatMap { Int($0) })

※失敗可能イニシャライザがわからない場合はこちらの記事で解説をしていますのでご覧ください。

Swiftとイニシャライザ


その他の高階関数

Sequenceの高階関数の一覧です。Optionalは先述のmapとflatMapのみです。クリックすると公式ドキュメントに飛びます。


補足: 高階関数の構文について

上記の高階関数の説明では、Swiftの後置クロージャー(trailing closure)と簡易引数名(shorthand argument name)を利用しています。

後置クロージャー(trailing closure)は関数の最後の引数がクロージャーの場合に以下のように最後の引数ラベルを省略し、クロージャーを関数の後に置いて記述できるという文法です。例えばUIViewのanimate関数では、以下のように2つの書き方ができます。

// UIViewのanimate関数

class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void)

// 後置クロージャーを利用しない場合

UIView.animate(withDuration: 2.0, animations: { label.alpha = 0 })

// 後置クロージャーを利用する場合
UIView.animate(withDuration: 2.0) { label.alpha = 0 }

また、クロージャーが引数を取る場合に、その引数名を省略できるという簡易引数名(shorthand argument name)の構文もあります。引数名を省略する場合は順に$0, $1, $2...という名前で参照できます。

// 簡易引数名を利用しない場合

let numbers = [1, 2, 3, 4, 5]
let result = numbers.filter { number in number % 2 == 0 }

// 簡易引数名を利用する場合
let numbers = [1, 2, 3, 4, 5]
let result = numbers.filter { $0 % 2 == 0 }


参考