Swift言語のfor-in文にハマった話

# 自分への備忘録も兼ねてここにまとめておく



Swift 3.0からC言語風の**for**文は廃止され、**for-in**だけのシンタックスとなった。


```c:C言語
for (int i = 0; i < 10; i++) {
  //処理
}
```

上と同等なことをswiftで書くと**↓**こうなる。

```swift:swift言語（Swift 3.0以降）
for i in 0 ..< 10 {
  //処理
}
```

これだけなら、なんら困ることはない。
今回ハマったケースは、終了条件にかかわる変数をループ内で更新している場合である。

```c:C言語なら
int last = 10;
for (int i = 0; i < last; i++) {
  //処理
  last = 11;

}
```

上の例では、ループ内の処理は11回実行される。しかし、swiftだと、

```swift:swift言語だと
var last = 10
for i in 0 ..< last {
  //処理
  last = 11

}
```

10回しかループしないのだ。これに気付かず、原因究明に時間を要してしまった。
swiftを使いはじめてかなりの年数が経つが初めての経験であった。

# for-in文はループ開始前にループ回数が決まる

**ループ開始前に式の評価が終わっている**ため、ループ内で終了条件にかかわる変数を更新しても（再評価されないので）、ループ回数は変わらないのだ。

同様に、配列の要素を順に処理する場合に、ループ内で要素数が変化（追加や削除）する場合でも、ループ前に評価された要素数となるので注意が必要だ。

```swift:例1（要素数が増える場合）
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]
```

```swift:例2（要素数が減る場合）
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をイテレートする場合も（ループ内でその要素を更新していても）、ループ前に評価された値のままだ。

```swift:例3
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**を使っても<font color=red><b>回避できない</b></font>ことを付け加えておく。

```swift:例4 (stride)
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**からの抜粋である。

![for-in.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/9af1eac8-066c-af83-8ddc-4910b4393383.png)

`beginning of the loop`という単語が見えるので、おそらく言語規定上の動作だと思われる。（英語はまったく自信がないので、違っていたらスミマセン）


# で、どうする？　

素直に、**while**文で書く。ただし、ループ変数の更新は、普通ならループの末尾に置くのが普通だが、continue文を使いたい場面が多々あるため、ループの先頭に置いた。

```swift:whileで代替
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

}
```

<hr>

**for-in**文はループ開始前に『ループ変数(<b><i>item</i></b>)』が決まるというお話でした。
（コンパイルオプションで変えられる、なんてことあるのかな？）
以上