Array(Sequence)のmap/forEachで途中リターン
これはなに?
途中リターン出来ます
実例
では実際にやってみましょう
簡単な例
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
実行結果は同じです。
解説
lazy
とprefix
を使って途中リターンを実現しています。
Sequence
のlazy
プロパティはLazySequence
を返します。
LazySequence
はLazySequenceProtocol
という遅延評価を行う各種メソッドを要求するプロトコルに準拠したタイプです。
LazySequenceProtocol
に準拠するタイプにはLazySequence
以外にもいろいろありますので気になる方は調べてください。
ここでいう遅延評価とはmapなどの関数を引数に取るメソッドが即座に渡された関数を実行するのではなく、実際に値が必要になるまで関数の適用(評価)を遅延させる事をいいます。
map以外にもprefixやfilterなども遅延評価されます。
もし、関数g
にlazy
がなかった場合を想像してください。
元となるSequence
の要素は無限にありますからmap
関数の処理が終わる事はありません。(実際には無限ではないのでInt.maxを超えたところでクラッシュします)
lazy
とprefix
を使う事で必要な要素のみを処理し途中で終了させる事が出来るのです。
終了条件の部分に注目してみましょう。
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