自分への備忘録も兼ねてここにまとめておく
Swift 3.0からC言語風のfor文は廃止され、for-inだけのシンタックスとなった。
for (int i = 0; i < 10; i++) {
//処理
}
上と同等なことをswiftで書くと↓こうなる。
for i in 0 ..< 10 {
//処理
}
これだけなら、なんら困ることはない。
今回ハマったケースは、終了条件にかかわる変数をループ内で更新している場合である。
int last = 10;
for (int i = 0; i < last; i++) {
//処理
last = 11;
}
上の例では、ループ内の処理は11回実行される。しかし、swiftだと、
var last = 10
for i in 0 ..< last {
//処理
last = 11
}
10回しかループしないのだ。これに気付かず、原因究明に時間を要してしまった。
swiftを使いはじめてかなりの年数が経つが初めての経験であった。
for-in文はループ開始前にループ回数が決まる
ループ開始前に式の評価が終わっているため、ループ内で終了条件にかかわる変数を更新しても(再評価されないので)、ループ回数は変わらないのだ。
同様に、配列の要素を順に処理する場合に、ループ内で要素数が変化(追加や削除)する場合でも、ループ前に評価された要素数となるので注意が必要だ。
var A = Array(0 ..< 10)
for n in 0 ..< A.count {
if n > 0 && n.isMultiple(of: 3) {
A.insert(n + 10, at: n)
}
print(n, A.count, A, separator: "\t")
}
//結果
n A.count A
0 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3 11 [0, 1, 2, 13, 3, 4, 5, 6, 7, 8, 9]
4 11 [0, 1, 2, 13, 3, 4, 5, 6, 7, 8, 9]
5 11 [0, 1, 2, 13, 3, 4, 5, 6, 7, 8, 9]
6 12 [0, 1, 2, 13, 3, 4, 16, 5, 6, 7, 8, 9]
7 12 [0, 1, 2, 13, 3, 4, 16, 5, 6, 7, 8, 9]
8 12 [0, 1, 2, 13, 3, 4, 16, 5, 6, 7, 8, 9]
9 13 [0, 1, 2, 13, 3, 4, 16, 5, 6, 19, 7, 8, 9]
var A = Array(0 ..< 10)
for n in 0 ..< A.count {
if n > 0 && n.isMultiple(of: 3) {
A.removeLast()
}
print(n, A.count, A, separator: "\t")
}
//結果
n A.count A
0 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3 9 [0, 1, 2, 3, 4, 5, 6, 7, 8]
4 9 [0, 1, 2, 3, 4, 5, 6, 7, 8]
5 9 [0, 1, 2, 3, 4, 5, 6, 7, 8]
6 8 [0, 1, 2, 3, 4, 5, 6, 7]
7 8 [0, 1, 2, 3, 4, 5, 6, 7]
8 8 [0, 1, 2, 3, 4, 5, 6, 7]
9 7 [0, 1, 2, 3, 4, 5, 6]
要素数が増えようが減ろうがお構いなしに、ループ開始時に評価した内容でループする。
例2だと、配列外参照必至のパターンだ。
Collectionをイテレートする場合も(ループ内でその要素を更新していても)、ループ前に評価された値のままだ。
var n = 0
var A = Array(0 ..< 10)
for a in A {
A[(n + 1) % 10] += 10
print(n, a, A, separator: "\t")
n += 1
}
//結果
n a A
0 0 [0, 11, 2, 3, 4, 5, 6, 7, 8, 9]
1 1 [0, 11, 12, 3, 4, 5, 6, 7, 8, 9]
2 2 [0, 11, 12, 13, 4, 5, 6, 7, 8, 9]
3 3 [0, 11, 12, 13, 14, 5, 6, 7, 8, 9]
4 4 [0, 11, 12, 13, 14, 15, 6, 7, 8, 9]
5 5 [0, 11, 12, 13, 14, 15, 16, 7, 8, 9]
6 6 [0, 11, 12, 13, 14, 15, 16, 17, 8, 9]
7 7 [0, 11, 12, 13, 14, 15, 16, 17, 18, 9]
8 8 [0, 11, 12, 13, 14, 15, 16, 17, 18, 19]
9 9 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
以上のことは、strideを使っても、forEachを使っても回避できないことを付け加えておく。
let A = Array(0 ..< 10)
var step = 1
for n in stride(from: 0, to: A.count, by: step) {
step += 1
print(n)
}
//結果
0
1
2
3
4
5
6
7
8
9
言語として規定があるのか?
以下はSwift Languageからの抜粋である。
beginning of the loop
という単語が見えるので、おそらく言語規定上の動作だと思われる。(英語はまったく自信がないので、違っていたらスミマセン)
で、どうする?
素直に、while文で書く。ただし、ループ変数の更新は、普通ならループの末尾に置くのが普通だが、continue文を使いたい場面が多々あるため、ループの先頭に置いた。
var i = 0, last = 10
while i < last {
let n = i // for n in 0 ..< last
i += 1
//処理
//if A[n] == x { continue }
last = 11
}
for-in文はループ開始前に『ループ変数(item)』が決まるというお話でした。
(コンパイルオプションで変えられる、なんてことあるのかな?)
以上