Swiftっぽい配列の処理をするためのクロージャの使い方を学んだ（map, sort, filter）

Swiftを真面目に勉強し始めて、まだ一ヶ月たってないくらいなんですが、
やはりクロージャに苦手意識があります。

[クロージャって結局何なのか](https://qiita.com/nkj11/items/949747037030d8e171f4)
以前こんなQiita記事も書いたんですが、この段階では教科書的な定義はわかったものの、
じゃあ具体的に使うケースは……？　というのはよくわからないままでした。

そんなとき、配列扱ってるときに、要素の追加とかをいちいちループさせてたんですが、
mapってやつを使って、引数にクロージャ与えると、たった一行で実装できる、というのを知り、
えっクロージャ強いじゃん、となりまして、勉強してみました。

# Swiftっぽくない書き方
mapを使わないで書くと、こんな感じです。

```swift:標準入力を二次元配列に
//1 3 1
//3 2 2
//2 3 5
//3 4 4
//1 6 6
//こんな標準入力が与えられるので、[[1,3,1],[3,2,2],...]みたいな感じで初期化したい
let numberOfInputLines = 5
var input_lines = [String]() //標準入力をとりあえず受け取る
var outArray: [[Int]] = [[Int]]() //変換先の二次元配列

for i in 0..<numberOfInputLines {
    input_lines.append(readLine()!)
    let splits: [String] = input_lines[i].components(separatedBy: " ") 
    outArray.append([Int(splits[0])!, Int(splits[1])! , Int(splits[2])!])
}
print(outArray) //[[1, 3, 1], [3, 2, 2], [2, 3, 5], [3, 4, 4], [1, 6, 6]]
```
これでもやりたいことはできているものの、

- やりたいことに対してコードが長い
- .append()の繰り返しがなんかダサい
- outArrayは初期化したあとは内容変更しないのに、.append()で要素追加したいがためにvarで指定している

などの問題があります。

# Swiftっぽい書き方
.mapを使ってやると、こんなふうに書けます。

```swift:標準入力を二次元配列に（map版）
let numberOfInputLines = 5
var input_lines = [String]()
var splits =  [[String]]() //for文の外に

for i in 0..<numberOfInputLines {
    input_lines.append(readLine()!)
    splits.append(input_lines[i].components(separatedBy: " ")) //[["1", "3", "1"], ["3", "2", "2"], ["2", "3", "5"], ["3", "4", "4"], ["1", "6", "6"]]
}    

let outArray = splits.map { [Int($0[0])!, Int($0[1])!, Int($0[2])!] }
/*
2019/1/21 追記
splitsという配列をつくっていますが、mapを二重で使うと不要です。
ただちょっと説明しづらいですね。。。
let outArray = input_lines.map { $0.components(separatedBy: " ").map { Int($0)! } }
*/

print(outArray) //[[1, 3, 1], [3, 2, 2], [2, 3, 5], [3, 4, 4], [1, 6, 6]]
```
すみません書いてて思ったんですが、あんまりいいサンプルじゃなかったですね……
paizaのA問題解いてるときにこういう処理が必要だったんです。

# .mapを解読する
```swift:よくある書き方
let outArray = splits.map { [Int($0[0])!, Int($0[1])!, Int($0[2])!] }
```
さてmapの書き方ですが、皆さんは意味がわかりますか？
僕は全然わかりませんでした。
なぜわかんないのか調べていくと、**いろんなものが省略されているから**だとわかりました。

敢えて一切省略せず、冗長に書くと、mapは下記のようにかけます。

```swift:敢えてクロージャを一切省略値使わずに冗長に書く
let outArray = splits.map({ (element: [String]) -> [Int] in 
    return [Int(element[0])!, Int(element[1])!, Int(element[2])!]
})
```
全然雰囲気変わりましたね。
でもやってることは同じです。
いちいちこんな書き方しなければいけないんだったら、mapなんて使わないで、
ループでぐるぐるappendさせてもそんな変わんないような気もしますね。

では何が省略されていたのか、ひとつずつ見ていきましょう。

### 1.トレイリングクロージャ
まず最初の書き方では、
.mapは関数なのに、.map()のカッコが書かれてなかったですね。

<img width="766" alt="70DE69B5-321E-42FB-8C2E-2FEFE19727AF.png" src="https://qiita-image-store.s3.amazonaws.com/0/333454/fb60e315-48b0-39e4-6be6-2a9d989657a1.png">

これはトレイリングクロージャ（接尾クロージャとも）と言われる記法で、
関数の最後の引数がクロージャの場合は、()の外に書ける記法です。
冗長な書き方を見てもわかるとおり、クロージャが引数だと、
{}の外に()を包まないといけないので、可読性悪くなりますね。

### 2.型推論による省略
クロージャは引数と返り値を持ちますが、
人が書いてるコード見るとあまり明示的に書いてるケースの方が少ない気がします。
<img width="766" alt="7D332E82-0246-4973-84DC-4FB1E499381E.png" src="https://qiita-image-store.s3.amazonaws.com/0/333454/a3c0027e-4c5b-89ee-acda-05a7f1e3db34.png">

そもそもmapの関数定義は、下記のようになっています。

```swift:mapの関数定義
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
```
Array型はmapというメソッドを持っていて、
Array型の各要素をクロージャに内包される形で受け取って、
なんかしらの処理をして、最終的にArray<T>型にして返してやる、というのがmapです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/333454/21d576ce-2aa6-2c3e-f715-4d23d28714c3.png)
イメージ図としてはこんな処理です。

クロージャの引数・返り値に対しては、型推論が効くので、省略できます。
今回のケースでは、String型の一次元配列を要素として受け取って、
Int型の一次元配列として吐き出し、最終的に[[Int]]の二次元配列になります。
もしプログラム上、コンパイラが型を推測できないような処理を書いちゃうと、コンパイルエラーになります。

### 3.簡略引数名
冗長な記法だと、配列の各要素をelementという引数名を使って受け取ってました。

<img width="766" alt="17DB30F0-1137-45FF-A575-32E50DDC9E83.png" src="https://qiita-image-store.s3.amazonaws.com/0/333454/86b7fcf2-338e-e8ae-2491-9a9c21d47887.png">


クロージャは簡略引数名というのが使えます。
$0で配列の各要素を示します。
これを使うと、上記elementという引数名は省略できますね。
引数名を省略する場合、inキーワードも一緒に省略する習わしみたいです。

### 4.暗黙的なreturn（Implicit Returns）
だいぶ省略できましたね。
最後にreturnも消しちゃいましょう。
クロージャは文が一文しかない場合は、その計算結果を出力としてreturnします。
これを暗黙的なreturn（Implicit Returns）と言うそうです。

ここまで紐解いていくと、最初の省略形がいかに多くの情報を省略した、簡潔な書き方なのかがわかりますね。

# sortとfileter
思いの外mapの説明だけで時間を食ったので、
当初sortとかfilterとかも調べた結果を書こうと思ってたのですが、
ちょっとしんどいので、参考サイトを見てください。
sortやfilterだとクロージャを使って条件を指定できます。

# 関数型っぽい書き方についての個人的感想
話題になってる[Haskellをかける少女](https://qiita.com/jzmstrjp/items/11dcd3ec26027ff30214)とかでも、
関数型プログラミングっぽい記法が紹介されていますが、
確かに小難しい処理を簡潔に書けるので、嬉しい気はします。

ただそれはソースコード的な綺麗さだけの話らしく、
（言語にもよるでしょうが）
パフォーマンスがよくなるわけではないようです。

map/sort/filterあたりは使えなくても、自分で制御文書けば代用できるのですが、
折角Swiftで書くんであれば、使いこなせるようになりたいですね。

# 参考サイト
[Apple公式](https://developer.apple.com/documentation/swift/array)
[Swiftのmap, filter, reduce（などなど）はこんな時に使う！](https://qiita.com/motokiee/items/cf83b22cb34921580a52)