🎻

Array(Sequence)のmap/forEachで途中リターン

2021/03/13に公開

これはなに?

途中リターン出来ます

実例

では実際にやってみましょう

簡単な例

0から始まる無限数列を2倍しながら途中まで順に表示します。(無限ではない)

forを使った方法

func f() {
    for i in (0...) {
        let j = i * 2
        if j > 10 {
            return
        }
        print(j)
    }
}

f()

実行結果

0
2
4
6
8
10

forEachを使った方法

func g() {
    (0...)
        .lazy
        .map { $0 * 2 }
        .prefix { $0 <= 10 }
        .forEach { print($0) }
}

g()

実行結果

0
2
4
6
8
10

実行結果は同じです。

解説

lazyprefixを使って途中リターンを実現しています。

SequencelazyプロパティはLazySequenceを返します。
LazySequenceLazySequenceProtocolという遅延評価を行う各種メソッドを要求するプロトコルに準拠したタイプです。
LazySequenceProtocolに準拠するタイプにはLazySequence以外にもいろいろありますので気になる方は調べてください。
ここでいう遅延評価とはmapなどの関数を引数に取るメソッドが即座に渡された関数を実行するのではなく、実際に値が必要になるまで関数の適用(評価)を遅延させる事をいいます。
map以外にもprefixやfilterなども遅延評価されます。

もし、関数glazyがなかった場合を想像してください。
元となるSequenceの要素は無限にありますからmap関数の処理が終わる事はありません。(実際には無限ではないのでInt.maxを超えたところでクラッシュします)
lazyprefixを使う事で必要な要素のみを処理し途中で終了させる事が出来るのです。

終了条件の部分に注目してみましょう。
forを使った場合の終了条件は

if j > 10 {
    return
}

です。lazyを使用した場合の終了条件は

    .prefix { $0 <= 10 }

です。
これらの条件はちょうど逆になっています。

終了条件が

if condition {
    return // あるいは break
}

となっている途中リターンのあるforループは

    .prefix { !condition }

を終了条件としたLazySequenceで置き換える事が可能であるという事です。

2重ループ

2重ループの内側のループで行われる途中リターンはどうでしょう。

forを使った方法

func f() {
    for i in (0...) {
        for j in (0...5) {
            if i * j > 10 {
                return 
            }
            print(i, j)
        }
    }
}

f()

実行結果

0 0
0 1
0 2
0 3
0 4
0 5
1 0
1 1
1 2
1 3
1 4
1 5
2 0
2 1
2 2
2 3
2 4
2 5
3 0
3 1
3 2
3 3

forEachを使った方法


func g() {
    (0...)
        .lazy
        .flatMap { i in
            (0...5)
                .lazy
                .map { j in (i, j, i * j) }
        }
        .prefix { $0.2 <= 10 }
        .forEach { print($0.0, $0.1) }
}

g()

実行結果

0 0
0 1
0 2
0 3
0 4
0 5
1 0
1 1
1 2
1 3
1 4
1 5
2 0
2 1
2 2
2 3
2 4
2 5
3 0
3 1
3 2
3 3

こちらが同等の処理です。

解説

初見だとかなりややこしく見えますが、基本的には同じです。

if condition {
    return // あるいは break
}

    .prefix { !condition }

に書き換えています。

2重にループを行う場合mapを使うと2次元配列になるためflatMapを使用する必要がある事に注意してください。
また、イテレータの値が必要な場合はタプルなどを利用して次の処理に渡す必要があります。

ちょっと寄り道

途中リターンではありませんが、条件文とcontiuneをつかって処理を飛ばす事もよくあります。
これもLazySequenceを使って書く事が可能です。

forを使った方法


func f() {
    for i in (0..<10) {
        if i.isMultiple(of: 2) {
            continue
        }
        print(i)
    }
}

f()

実行結果

1
3
5
7
9

forEachを使った方法

func g() {
    (0..<10)
        .lazy
        .filter { !$0.isMultiple(of: 2) }
        .forEach { print($0) }
}

g()

実行結果

1
3
5
7
9

解説

contiueを使った処理の省略は filterを使って書き換える事が可能です。
この場合も条件は逆になります。

if condition {
    continue
}


   .filter { !condition }

で書き換える事が出来ます。

まとめ

forなどの繰り返し構文による途中リターンのほとんどはArray/Sequenceのmap/forEachなどに変換可能です。

メリットは?

え?
それ、必要ですか?

あー、そこになかったらないですね。

おまけ

これはfor/whileじゃ無理でしょ?という途中リターンの例をお待ちしています。

Discussion