swift-algorithmsを初めて触ってみた。

こんにちは。TRILL iOSアプリを開発している[shira-shun](https://qiita.com/shira-shun)です。
これは[delyアドベントカレンダー](https://adventar.org/calendars/6455)16日目の記事となります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470777/9a49dd08-fb7f-45c1-c7c2-039304a3e1d9.png" width="50%"> 

昨日は [Yuya Masuda](https://note.com/nanami_1127)さんの[バックエンドエンジニアからみたTRILLというプロダクトの魅力](https://note.com/nanami_1127/n/n8d1950156af6)でした。

## はじめに
Xcode13からApple製のパッケージをSwiftPMから手軽に使用できるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470777/af38ddd3-6be5-9bc3-6c21-89d47232f729.png)


どのApple製パッケージも使ったことがない私は、一番上に表示されていた[swift-algorithms](https://github.com/apple/swift-algorithms)に興味を持ちました。
`swift-algorithms`の存在は以前から知っていましたが、パッケージ名が`アルゴリズム`なのでアルゴリズムについて全く知識がない私にとっては縁のないものだと思っていました。

とりあえず、READMEを読んでみたら全然難しくなく、すぐ業務や個人開発でも使えるやんって思い少し触ってみました。

触った機能の中で今すぐにでも、取り入れられそうな機能を標準で書いたパターンと`swift-algorithms`で書いたパターンを比べていくつか紹介したいと思います！！

## nilが含まれる配列からnilを除外したい
### before
```swift
let array = [1, nil, 2, nil, 3, nil, 4]
let safeArray = array.compactMap { $0 }
print(safeArray)

// [1, 2, 3, 4]
```

### after
```swift
let array = [1, nil, 2, nil, 3, nil, 4]
let safeArray = array.compacted()
print(safeArray)

// [1, 2, 3, 4]
```

## 複数の値をランダムで取得したい
### before
```swift
let array = 1 ... 10000
let randomElements = (0 ... 10).compactMap { _ in array.randomElement() } // 重複する可能性がある
print(randomElements)

// [9447, 5025, 4364, 2682, 9800, 31, 1450, 1694, 9268, 8418, 3718]
```

### after
```swift
let array = 1 ... 10000
let randomElements = array.randomSample(count: 10) // 重複しない、取得件数が範囲より多い場合は範囲の分だけ取得する
print(randomElements)

// [1020, 8120, 634, 6403, 1735, 3809, 365, 4680, 7915, 4209]

// 順序も保証したい場合
let array = 1 ... 10000
let randomElements = array.randomStableSample(count: 10)
print(randomElements)

// [4846, 5317, 5388, 6455, 6971, 7493, 7533, 9096, 9282, 9533]
```

## ユニークな配列を作成したい
### before
```swift
let array = [99, 10, 13, 23, 13, 13, 19, 32, 43, 32, 10]
let unique = array.reduce([], { $0.contains($1) ? $0 : $0 + [$1] }).sorted()
print(unique)

// [10, 13, 19, 23, 32, 43, 99]
```

### after
```swift
let array = [99, 10, 13, 23, 13, 13, 19, 32, 43, 32, 10]
let unique = array.uniqued().sorted()
print(unique)

// [10, 13, 19, 23, 32, 43, 99]
```

#### [おまけ(頭文字でユニークかどうか判断する方法)](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Unique.swift#L100)
```swift
let animals = ["dog", "pig", "cat", "ox", "cow", "owl"]
let uniqued = animals.uniqued(on: { $0.first })
print(uniqued)
// ["dog", "pig", "cat", "ox"]
```


## 配列を結合したい
### before
```swift
let array = Array(1 ... 5)
let result = array + array
print(result)

// [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
```

### after
```swift
let array = 1 ... 5
let result = Array(chain(array, array))
print(result)

// [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
```

## ２つの配列を結合したい
```swift
let seasons = ["winter", "spring", "summer", "fall"]
for year in 2017...2020 {
    for season in seasons {
        print("\(year): \(season)")
    }
}

// 2017: winter
// 2017: spring
// 2017: summer
// 2017: fall
// 2018: winter
// 2018: spring
// 2018: summer
// 2018: fall
// 2019: winter
// 2019: spring
// 2019: summer
// 2019: fall
// 2020: winter
// 2020: spring
// 2020: summer
// 2020: fall
```

### after
```swift
let seasons = ["winter", "spring", "summer", "fall"]
for (year, season) in product(1900...2020, seasons) {
    print("\(year): \(season)")
}

// 2017: winter
// 2017: spring
// 2017: summer
// 2017: fall
// 2018: winter
// 2018: spring
// 2018: summer
// 2018: fall
// 2019: winter
// 2019: spring
// 2019: summer
// 2019: fall
// 2020: winter
// 2020: spring
// 2020: summer
// 2020: fall
```

## 最小値、最大値を取得したい(複数個可能)
### before
```swift
let numbers = [7, 1, 6, 2, 8, 3, 1, 9]
let smallestThree = numbers.sorted().prefix(3)
print(smallestThree)

// [1, 1, 2]
```

### after
```swift
let numbers = [7, 1, 6, 2, 8, 3, 1, 9]
let smallestThree = numbers.min(count: 3, sortedBy: <)
print(smallestThree)

// [1, 1, 2]
```

## nilを含む配列から最初の一件(nilではない)を取得したい
### before
```swift
let array = [nil, 19090, nil, nil, 1323, nil]
let result = array.compactMap { $0 }.first
print(result)

// Optional(19090)
```

### after
```swift
let array = [nil, 19090, nil, nil, 1323, nil]
let result = array.firstNonNil({ $0 })
print(result)

// Optional(19090)
```

## 感想
- READMEが充実しすぎて、難しすぎてよくわからないものがなかった。
    - もっと早く出会いたかった...
- `compacted()`や`uniqued()`は真っ先に取り入れても良いかなと思った。
- いくつかテストコードを書いてパフォーマンステストを行ってみたが、必ずしもswift-algorithmsの関数が早いわけではない
    - 配列の個数が膨大だったり、明らかに処理が重そうなものに関しては明らかな性能差がでた。
- [トリミング](https://github.com/apple/swift-algorithms/blob/main/Guides/Trim.md)は実務案件で必ず出番がありそうな印象をもった。

#### どのアプリに導入しても損しないパッケージだった...
TRILLのチームにこれ導入してみないか？と相談して見ようと思います！

## 最後に
delyではエンジニア、デザイナー、PdMを積極採用しています。 
少しでも興味がありましたら、お話だけでもできればと思います。

https://dely.jp/recruit