ループ途中で抜ける処理をfilter()等で書き換える

Swiftを触っていると、色々なループをmap/forEachなどで置き換えたくなります。その1パターンの実装例を考えてみます。

目的のデータを探すためのループ

ループ処理のユースケースとして、下例のような「目的の値を取り出すために見つかるまでループ。見つかったら値を保持してbreak」という使い方があります。

let array = Array(0..<1000)

var ret = 0
for i in array {
    if i == 33 {
        ret = i
        break
    }
}

これを何も考えずfilter()で置き換えた場合のコードは次のとおりです。

// filterで目的の値を見つける
let ret1 = array.filter{ $0 == 33 }.first

シンプルですしこれでも良いように思いますが、実はこれだとパフォーマンスの問題を抱えてしまいます。Playgroundなどで実行回数を調べてみると分かりますが、filterは配列全体を一旦スキャンするため、要素数によっては大量の繰り返し処理が紛れ込むことになってしまうからです。

// filterで目的の値を見つける
let ret1 = array.filter{ $0 == 33 }.first // 1001times !

配列のサイズが数万だった場合には目も当てられません。無駄な処理を避けて、見つかったところで繰り返しを抜けるにはどうしたらいいでしょうか。

lazy

この場合、lazy を配列に付けてあげます。すると配列の値がオンデマンドで参照されるようになり、繰り返しが必要なところまでで止まります。

// lazy化した配列を使い、filterで目的の値を見つける
let ret1 = array.lazy.filter{ $0 == 33 }.first // 35times

上記のソースではfirstが得られた時点、つまりfiltertrueを返した時点で処理が終わり、それ以降は計算が行われません(34でなく35になっているのは、lazy)。 余分な処理を実行しなくてもすむようになりました。

他の方法があればぜひコメントなどで教えてください🙇