クロージャと高階関数
読了目安 約2分
定型句の { Int($0)! } の正体を明かす。map・filter・reduce とカスタムソートを使いこなす。
この章の目次
readLine()!.split(separator: " ").map { Int($0)! }。
入力と実行順序 で「そのまま覚える」とした定型句です。
この { ... } の正体を明かします。
クロージャ式
クロージャは、名前を持たない小さな関数です。
{ 引数 in 処理 } の形で書き、関数の引数としてその場で渡せます。
let a = [3, 1, 4, 1, 5]
print(a.sorted { x, y in x > y })
print(a.sorted { $0 > $1 })
print(a.sorted(by: >))3 行はすべて同じ降順ソートです。
$0 $1 は「1 番目・2 番目の引数」を指す省略記法です。
比較が演算子そのものなら、(by: >) まで縮められます。
map・filter・reduce
クロージャを引数に取る関数を高階関数と呼びます。 配列の加工は、次の 3 つで大半が書けます。
| 関数 | 働き | 例 |
|---|---|---|
map | 全要素を変換する | map { $0 * 2 } |
filter | 条件を満たす要素だけ残す | filter { $0 % 2 == 1 } |
reduce | 全要素を 1 つにまとめる | reduce(0, +) |
let a = [1, 2, 3, 4, 5]
print(a.map { $0 * 2 })
print(a.filter { $0 % 2 == 1 })
print(a.reduce(0, +))reduce(0, +) は「0 から始めて、全要素を + で足し込む」という意味です。
競プロでの実用形
定型句の後半は「split が返した Substring の配列を、map で Int の配列へ変換」でした。
もう読み解けるはずです。
let a = readLine()!.split(separator: " ").map { Int($0)! }
print(a.reduce(0, +))3 1 4 1 5カスタムソートも、クロージャ 1 つで書けます。
let words = ["swift", "go", "rust", "c"]
print(words.sorted { $0.count < $1.count })試してみよう: < を > に変えて、長い単語から並ぶことを確かめてください。
やり過ぎ注意
高階関数は、書けるからといって読みやすいとは限りません。 分岐や途中の状態が絡む処理は、for 文の方が素直に書けることが多いです。 「明日の自分が一読で追えるか」で使い分けてください。
学びどころ
| 概念 | 一言まとめ |
|---|---|
| クロージャ | 名前のない関数。{ 引数 in 処理 } |
$0 $1 | 引数の省略記法 |
map / filter / reduce | 変換・絞り込み・集約の高階関数 |
sorted(by: >) | 降順ソートの最短形 |
| 使い分け | 込み入る処理は for 文の方が読みやすい |
演習
1 行目に整数 N が入力されます。 2 行目に N 個の整数 A_1 … A_N が空白区切りで入力されます。
偶数だけの合計を出力してください。 偶数が 1 つもないときは 0 を出力します。
制約: 1 ≤ N ≤ 10^5、1 ≤ A_i ≤ 10^9
入力例:
5
1 2 3 4 5
出力例:
6模範解答
_ = readLine()
let a = readLine()!.split(separator: " ").map { Int($0)! }
print(a.filter { $0 % 2 == 0 }.reduce(0, +))filter で偶数だけ残し、reduce で合計します。
偶数が 1 つもなければ filter の結果が空になり、reduce は初期値 0 を返します。