[配列].appendは重いからmapを使う

#Qiita更新しました
数多くの考察コメント、本当にありがとうございました！

考察コメントを参考に更新していきたいと思います。

#結果から
まず、結果としてappendもmapも処理速度に`大差ない`という事です。


@YOCKOW さんがGoogleの[swift-benchmark](https://github.com/google/swift-benchmark)を使って
検証して下さったのですがコメント欄にもあるように以下の結果となりました。

```
name                         time          std        iterations
----------------------------------------------------------------
append                       151697.500 ns ±  46.06 %       6718
append with initial capacity 145791.500 ns ±  45.14 %       6828
map                          140519.000 ns ±  41.81 %       8355
```
計測対象としましては、

・`append`

・`append(事前にcapacityを確保)`

・`map`

の３つです。

結果を見て分かるように、`ほとんど違いがない`ですね。

そもそも私のQiitaに書いてあった`配列に要素を一個追加するたびにメモリを確保し直すということ`はしないそうで`Arrayのバッファ(データを一時的に蓄えておく記憶装置や記憶領域)`は拡張が必要な場合、capacityを`元の2倍以上で設定する`そうです。
[swift/ArrayShared.swift | GitHub](https://github.com/apple/swift/blob/a73a8087968f9111149073107c5242d83635107a/stdlib/public/core/ArrayShared.swift#L162-L182)



では何故、私の方では差が大きく生まれてしまったのでしょうか？

##原因①
まず、主な原因としましては`Playground`を使用していた点です。

この原因の詳細も有り難いことに、つよつよエンジニアの方がコメントして下さっているのですが
Playgroundでは`かなりの時間がエディタ右側の表示を行うために使用される`そうなんですね。

私のQiitaで書いてあるappendの処理の場合は、`1000回の表示`が行われるのに対して
mapの処理では`1度だけの表示`で済みます。

なので、その時間も含まれてしまって大きな差が生まれてしまったという訳です。

##原因②
純粋な`append`と`map`の比較になっていないという点です。

```swift
for v in 1...1000 {}

let _ = Array(1...1000).map({ $0.description })
```
この2つの処理は`用いられているイテレータが異なる上にmapには余計な変換処理が含まれている`ので
純粋な比較になっていないそうなんですね。

つまり、別物を比較しているので
そもそも検証は成立しないということです。

※詳しい説明は下の方で解説して下さっているので確認して下さい。

#結論
@takehito-koshimizu さんがコメントで仰っていたように
私も適材適所で`map`も`append`も使っていこうという結論に至りました。

もはや皆さんのお言葉を借りてばかりの更新内容となってしまいましたね。

また何か間違いや気づきがあればコメントして下さると有り難いです🙇‍♂️

#はじめに
コードレビューにて、メンターに`appendは重いからmapを使おう`と
指摘して頂いたのがことの始まり。

#appendの場合
ある配列を取得したい場合、大体がこんな感じのコードになると思います。

```swift
var a: [Int] = []
var b = [1, 2, 3, 4, 5]

for int in b {
   a.append(int)
}

print(a) // [1, 2, 3, 4, 5]
```

ただ、これは`めちゃくちゃ重い処理`になってしまうんですね。

appendの場合、コードを見て分かるようにaの配列は`空`ですので
要素数が決まっていません。

```swift
var a: [Int] = []
```
もちろん、この時点ではどのぐらい要素を代入するかというのは決まっていません。
ただの空の配列が定義されているだけです。

実際に要素数を決めていくのは、この部分ですね。

```swift
for int in b {
   // 要素数があるだけ処理を繰り返す
   a.append(b)
}
```
上記の処理を端末のメモリ上で表すと、こんな感じになります。

```swift
|a|配列をメモリ内に確保したよ|b|c|d|

↓ append

|a|配列をメモリ内に確保したよ|b|c|d| // 配列を代入したいのに空いていないから一個ずらすね

|a|配列をメモリ内に確保したよ|appendされた値1だよ|b|c|d|

↓ append

|a|配列をメモリ内に確保したよ|appendされた値1だよ|b|c|d| // 配列を代入したいのに空いていないから、また一個ずらすね

|a|配列をメモリ内に確保したよ|appendされた値1だよ|appendされた値2だよ|b|c|d|

// 以下ループ
.
.
.
.
```
このような処理が、要素の数だけ繰り返されます。

代入する要素が少なければ、まだ動くかもしれませんが
APIから返ってきた値などをappendするとなると、かなりの負荷になりそうですね。

#mapの場合
一個ずつ値を足していく`append`に対して`map`はどうなんでしょうか？

冒頭で出てきた処理を書き直してみましょう。

```swift
// appendの場合
var a: [Int] = []
var b = [1, 2, 3, 4, 5]

for int in b {
    a.append(int)
}

print(a) // [1, 2, 3, 4, 5]

// mapの場合
let c = b.map({ $0 })

print(c) // [1, 2, 3, 4, 5]
```
空配列に対して一個ずつ足していく`append`よりも
`map`は予めクロージャー内を確認し、一気に代入するので`処理が軽い`です。

無駄に空の配列を用意しなくてもいいですし
コード量も抑えることが出来ます。

メモリで表すとこんな感じです。

```swift
// let c = b.map({ $0 })の処理が走ったら...

|a|1?|2?|3?|4?|5?|b|c|d| // 要素数が5つの配列が入るらしいから空けとくよ

↓

|a|1|2|3|4|5|b|c|d| // 事前に入る要素数が決まっていたから、ずらす必要がなかったね
```
こんな感じで、`map`は効率よく新しい配列を作ってくれます。

#実際に計測してみた
`MacBookPro 2016`の`playground`で計測していきます。

計測用メソッドはこちらの記事を参考にしました↓
[SwiftやiOSの便利だけど忘れそうな小ネタ集](https://qiita.com/sakiyamaK/items/5a5d99749d8a0fa892de#%E5%AE%9F%E8%A1%8C%E6%99%82%E9%96%93%E8%A8%88%E6%B8%AC)

まずは計測用のクラスを作って、計測の処理をしてくれるメソッドを作っていきます。

```swift
final class Benchmark {
    static func measure(_ targetStr: String = "", block: () -> Void) {
        // 処理始まりの時間を取得
        let startTime = Date()
        // 計測対象のクロージャー
        block()
        // 処理終わりの時間を取得
        let elapsed = Date().timeIntervalSince(startTime) as Double
        // 計測した時間を見やすい文字列に変える
        let formatedElapsed = String(format: "%.3f", elapsed)
        // 出力する
        print("Benchmark: \(targetStr), 実行時間: \(formatedElapsed)(s)")
    }
}
```

このようなメソッドを呼び出して、blockクロージャ内で
`append`と`map`の処理を記述していきましょう。

処理内容としましては、`数値→文字列`に変換して
その変換された値が入った配列を作るといったものです。

それを`1000回`繰り返します。

```swift
Benchmark.measure("append") {
    var appendList: [String] = []
    
    for v in 1...1000 {
        appendList.append(v.description)
    }
}
```
`append`の場合、結果は以下のようになりました。

```
Benchmark: append, 実行時間: 0.715(s)
```
それでは、`map`も計測していきましょう。

```swift
Benchmark.measure("map") {
    let _ = Array(1...1000).map({ $0.description })
}
```
このような結果になりました。

```
Benchmark: map, 実行時間: 0.008(s)
```

計測結果は並べてみると、違いは歴然です！

これで`append`よりも`map`の方が
`処理が早い`ということが分かりました。

```
Benchmark: append, 実行時間: 0.715(s)
Benchmark: map, 実行時間: 0.008(s)
```

#おわりに
配列操作関数とか言われるぐらいだから
配列に何らかの処理をしたい時はバンバン使おうと思いました。