🎉 はじめに
Swiftを利用してiOSアプリを開発している初心者たちは、配列を扱うときにやはり for 文を使ってしまうのではないでしょうか。(少なくとも僕はそうでした)
僕の最初の記事で、平成最後のアドベントカレンダーの記事の一つとして、いくつかSwiftの高階関数を紹介したいと思います。
💻 この記事の環境
- Xcode 10.1
- Swift 4.2
🧐 その前に
僕はC言語を最初のプログラミング言語として習ったので、Swiftを勉強し始めた時はいつもこんなコードを書いてしまいました。
例えば、[1, 2, 3, 4, 5, 6, 7, 8] という配列が与えられ、その中の要素を全て二乗したい時は考えましょう。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var squareNumbers: [Int] = []
for number in numbers {
squareNumbers.append(number * number)
}
print(squareNumbers)
// 出力: [1, 4, 9, 16, 25, 36, 49, 64]
このように、二乗された要素を新しい配列にまとめることはできたが、for 文で処理を行うと squareNumbers という配列を初期化する手間が掛かると思わないでしょうか。よりSwiftらしい書き方があるはず、と僕はそう考えていました。
そこで、高階関数(map, compactMap, filter, reduce)の出番です。
💪🏻 高階関数
高階関数(こうかいかんすう、英: higher-order function)とは、第一級関数をサポートしているプログラミング言語において、関数(手続き)を引数にしたり、あるいは関数(手続き)を戻り値とするような関数のことである。
wikipedia参照
高階関数は、主に関数型言語やラムダ計算において使用される関数で、SwiftのFunctionalな書き方を理解するためにも、より簡潔なコード書くためにも、高階関数を学んでおきたい関数だと思います。
💡 map()
利用場面
-
mapは配列の全要素に処理を適用し、その処理をを施された配列を使いたい場合に使用します。
使い方
この前に例として上げた「配列の全要素を二乗したい」コードを振り返ると、やはり長いですね。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var squareNumbers: [Int] = []
for number in numbers {
squareNumbers.append(number * number)
}
print(squareNumbers)
// 出力: [1, 4, 9, 16, 25, 36, 49, 64]
これを map を使うと以下のように書き換えます。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var squareNumbers = numbers.map({ $0 * $0 })
print(squareNumbers)
// 出力: [1, 4, 9, 16, 25, 36, 49, 64]
どうでしょうか、かなり簡潔に書けたのではないでしょうか。
💡 filter()
利用場面
-
filterは配列の全要素に処理を適用し、条件にマッチする要素のみが含まれる配列を使いたい場合に使用します。
使い方
map と同じく、Int 型の配列を例として説明します。例えば、ある配列の中の偶数だけ取得したい時にどうすればいいのでしょうか。
まずは、いつもの for 文の書き方を見ていきましょう。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var evenNumbers: [Int] = []
for number in numbers {
if number % 2 == 0 {
evenNumbers.append(number)
}
}
print(evenNumbers)
// 出力: [2, 4, 6, 8]
できないわけではないですが、やはりSwiftらしく書きたいですね。if 文をなくすとしたらどうなりますか。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var evenNumbers: [Int] = []
for number in numbers where number % 2 == 0 {
evenNumbers.append(number)
}
print(evenNumbers)
// 出力: [2, 4, 6, 8]
少しは簡潔に書けましたね、「これも一行で解決できるのでは」と思わないでしょうか。
もちろん、filter を使えばできます。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var evenNumbers = numbers.filter({ $0 % 2 == 0 })
print(evenNumbers)
// 出力: [2, 4, 6, 8]
これで map と同じように一行で簡潔に書けます。
💡 reduce()
利用場面
-
filterは配列の全要素を一つの要素にまとめる場合に使用します。
使い方
「配列の全要素を足す」という時を考えましょう。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var sum: Int = 0
for number in numbers {
sum += number
}
print(sum)
// 出力: 36
またこのような長いコードを見ると、「for 文はもう使いたくない」と考えるのではないでしょうか。早速、reduce を使って書き換えてみると。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var result = numbers.reduce(0, +)
print(result)
// 出力: 36
これだけで完了です。一つ注意してほしいのは reduce の第一引数は「初期値」で、第二引数は「演算子」です。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var result1 = numbers.reduce(0, +)
var result2 = numbers.reduce(1, +)
print(result1, result2)
// 出力: 36 37
上の例で書いてある通り、初期値を変えると、結果も変えてしまうので、これを気をつけて使いましょう。
let numbers: [Int] = [1, 2, 3]
var result = numbers.reduce(1, *)
print(result)
// 出力: 6
さらに、このように普段よく使う演算子 +, -, * を入れ替えてもオーケーです。
🛠 メソッドチェーン
ここまで、map, filter, reduce について簡単な使い方を紹介しましたが、これらの高階関数をまとめて使う例も一緒に紹介しようと思います。
例えば、[1, 2, 3, 4, 5, 6, 7, 8] という配列が与えられ、この配列の全要素をまず二乗にして、そのうち 20 より小さい要素の和を求めたい時はどうすればいいでしょうか。
二乗した配列は [1, 4, 9, 16, 25, 36, 49, 64] なので、そのうち 20 より小さい要素は [1, 4, 9, 16] で、全部足すと 30 になるはずです。
ここで、for 文を使った例は省略し、メソッドチェーンを利用して今までの高階関数を一気に使うと。
let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
var result = numbers.map({ $0 * $0 }).filter({ $0 < 20 }).reduce(0, +)
print(result)
// 出力: 30
やはり一行で簡潔に結果を正しく計算できました。
🤩 まとめ
以上、高階関数についてよく使うものについて、簡単な使い方の紹介でした。
いかがでしょうか、これらの関数を理解するには時間がかかるかもしれないが、使い方を慣れれば開発の効率が一気に上がり、コードもより簡潔に書けると思いますので、ぜひ使ってみてください。
他にも、compactMap, sort, 'firstIndex' などたくさんの関数があるので、興味がある方はいろいろ調べてもらえばと思います。
最後に、ここまで読んでいただいてありがとうございました。
僕は留学生なので、日本語が変だと思われるところがあるかもしれないが、この記事でSwiftをよりSwiftらし書けるようになったら嬉しいです。