ズンドコキヨシ with Combine

### TL; DR
* 3年前に流行ったツイートと[RxSwiftを使った記事](https://qiita.com/bricklife/items/4bf8c0e17043498f4452)を思い出したのでCombineで再現してみました

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ズンドコキヨシをCombineで再現してしまった...</p>&mdash; mshrwtnb (@mshrwtnb_) <a href="https://twitter.com/mshrwtnb_/status/1187739604033724416?ref_src=twsrc%5Etfw">October 25, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

* 元ネタはこちら

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">Javaの講義、試験が「自作関数を作り記述しなさい」って問題だったから<br>「ズン」「ドコ」のいずれかをランダムで出力し続けて「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら「キ・ヨ・シ！」って出力した後終了って関数作ったら満点で単位貰ってた</p>&mdash; てくも (@kumiromilk) <a href="https://twitter.com/kumiromilk/status/707437861881180160">March 9, 2016</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

### 0. ゴールの確認
* 「ずん」「どこ」「き・よ・し！」の出力はすべてreceiveValueで受け取る`String`とする(= assignしようと思えばできる状態を目指す）。記事では`input:○○○`の`○○○`で表現する。
* Publisherの`print(_:to:)`はデバグ用に使う（回答としてカウントしない）
* 一度「き・よ・し！」を出力したら処理を終了する

### 1. ずんどこ川の水源を作る
まずは「ずん」または「どこ」が湧き出る水源を作っていく。
勢いよく湧き出て欲しいので、0.1秒間隔で出力していこう。

iOS 13から追加された`Timer.publish(every:tolerance:on:in:options:)`を使っていく。

```swift
    let ずんどこ水源 =
        Timer.publish(every: 0.1, on: .main, in: .default)
            .autoconnect()
            .map { _ in Int.random(in:0..<2) == 0 ? "ずん" : "どこ" }
            .share()

    self.cancellable =
        ずんどこ水源
        .sink(receiveCompletion: { _ in
            print("===完===")
        }, receiveValue: {
            print("input: \($0)")
        })
```

結果はこちら。
![zundoko-fountain](https://media.giphy.com/media/IeS4m3S3IUr8dBZTOK/giphy.gif)

**良い。すごく良い。勢いある湧出量だ。**

### 2. センサー付きの水門づくり
上述のとおり、ずんどこ水源からは「ずん」または「どこ」が1語ずつ流れてくる。

検知したいのは「ずん、ずん、ずん、ずん、どこ」の5要素なので、FIFO方式でずんどこが5つだけたまる溜池を作る。
そして、溜まった要素がお目当てのパターンか判定するセンサーを作る。
もしパターンを検知できたら水門を閉じる。
いたずらにずんどこを溜めない。

```swift
    let センサー付き水門 =
        ずんどこ水源
            .scan([], { (reservoir: [String], element: String) -> [String] in
                // 溜池
                var newReservoir = Array(reservoir + [element])
                if newReservoir.count > 5 {
                    newReservoir.removeFirst()
                }
                return newReservoir.prefix(5).map { $0 }
            })
            .allSatisfy { $0 != ["ずん", "ずん", "ずん", "ずん", "どこ"] }
            .map { _ in  }
            .share()
```

### 3. 検知後「き・よ・し！」を知らせる
`()`が流れてくる仕組みを活用し、「き・よ・し！」と流れるスピーカーを作ろう。

```swift
   let スピーカー = センサー付き水門.map { _ in "き・よ・し！" }
```

これでも悪くはない。

しかし、贅沢を言えば「どこ」と「き・よ・し！」の間には"ため"が欲しい。
そこで1秒くらい"ため"を作ろう。
結果には影響を与えない形で"ため"を確認できるようprintしておこう。

```swift
    let スピーカー = センサー付き水門.map { _ in "き・よ・し！" }
                                .handleEvents(receiveOutput: { _ in
                                    print("（せーの...!）")
                                })
                                .delay(for: .seconds(1.0), scheduler: RunLoop.main)
```

### 4. 整備されたずんどこ川の全貌
野川状態だった、ずんどこ川にも必要な設備が揃った。
あとはこれらをうまく組み合わせていく。
これで、河川管理もバッチリだ。

```swift
let ずんどこ水源 =
    Timer.publish(every: 0.1, on: .main, in: .default)
        .autoconnect()
        .map { _ in Int.random(in:0..<2) == 0 ? "ずん" : "どこ" }
        .share()

let センサー付き水門 =
    ずんどこ水源
        .scan([], { (reservoir: [String], element: String) -> [String] in
            // 溜池
            var newReservoir = Array(reservoir + [element])
            if newReservoir.count > 5 {
                newReservoir.removeFirst()
            }
            return newReservoir.prefix(5).map { $0 }
        })
        .allSatisfy { $0 != ["ずん", "ずん", "ずん", "ずん", "どこ"] }
        .map { _ in  }
        .share()

let スピーカー = センサー付き水門.map { _ in "き・よ・し！" }
                            .handleEvents(receiveOutput: { _ in
                                print("（せーの...!）")
                            })
                            .delay(for: .seconds(1.0), scheduler: RunLoop.main)

self.cancellable =
    ずんどこ水源
    .prefix(untilOutputFrom: センサー付き水門) // センサーが検知するまで、ずんどこを流す
    .merge(with: スピーカー) // 検知したら、き・よ・し！を流す
    .sink(receiveCompletion: { _ in
        print("===完===")
    }, receiveValue: {
        print("input: \($0)")
    })
```

よし、最後にこれを動かしてみよう。
...   
....   
.....   

![output](https://media.giphy.com/media/QvT6AwZyySjtVRiE7U/giphy.gif)

💪💪💪💪💪💪💪💪💪💪💪💪💪💪💪


#### あとがき
ふざけているようですが、トライしてみるとオペレーターの挙動に詳しくなります
もっと良い実装例があったら[@mshrwtnb](https://twitter.com/mshrwtnb_)まで教えていただけると勉強になります
https://github.com/mshrwtnb/zundoko-combine にソースを置きました。

以上