Swift教室 Swift と競技プログラミングの教室

クロージャと高階関数

読了目安 約2分

定型句の { Int($0)! } の正体を明かす。map・filter・reduce とカスタムソートを使いこなす。

この章の目次

readLine()!.split(separator: " ").map { Int($0)! }入力と実行順序 で「そのまま覚える」とした定型句です。 この { ... } の正体を明かします。

クロージャ式

クロージャは、名前を持たない小さな関数です。 { 引数 in 処理 } の形で書き、関数の引数としてその場で渡せます。

Swift
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, +)
Swift
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 の配列を、mapInt の配列へ変換」でした。 もう読み解けるはずです。

Swift
let a = readLine()!.split(separator: " ").map { Int($0)! }
print(a.reduce(0, +))
入力例
3 1 4 1 5

カスタムソートも、クロージャ 1 つで書けます。

Swift
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
模範解答
Swift
_ = readLine()
let a = readLine()!.split(separator: " ").map { Int($0)! }
print(a.filter { $0 % 2 == 0 }.reduce(0, +))

filter で偶数だけ残し、reduce で合計します。 偶数が 1 つもなければ filter の結果が空になり、reduce は初期値 0 を返します。