for文やforEachで要素のindexを取得したい際、よく使われるのはenumerated()メソッドと言われているイメージがあります。
以下のようなコードです。
let johnny = ["J", "O", "H", "N", "N", "Y", "🧑💻"]
for (index, value) in johnny.enumerated() {
print(johnny[index])
}
J
O
H
N
N
Y
🧑💻
しかし、Apple公式ドキュメントはfor文やforEachで要素のindexを取得する際はenumerated()メソッドではなく、zip()メソッドを使用することを推奨しています。
To iterate over the elements of a collection with its indices, use the zip(::) function.
訳:コレクションの要素とそのインデックスを反復処理するには、zip(::) 関数を使用します。
引用: Apple公式Doument- enumerated()
その理由についてApple公式ドキュメントには次のように書かれていました。
When you enumerate a collection, the integer part of each pair is a counter for the enumeration, but is not necessarily the index of the paired value. These counters can be used as indices only in instances of zero-based, integer-indexed collections, such as Array and ContiguousArray. For other collections the counters may be out of range or of the wrong type to use as an index.
訳:コレクションを列挙するとき、各ペアの整数部は列挙用のカウンタであるが、必ずしもペアとなる値のインデックスではない。これらのカウンタは、Array や ContiguousArray などのゼロベースで整数のインデックスを持つコレクションのインスタンスでのみ、インデックスとして使用することができます。他のコレクションでは、カウンタは範囲外であったりインデックスとして使用するのに不適切な型であったりします。
筆者は初めてこの文章に目を通したとき、フワッと分かるよな気もするけど、分からないような...そんな気持ちになり、『とにかく、enumerated()が不適切な事例が存在するのかな』と解釈しました。
その事例を交えて解説していきます。
enumerated()メソッドでエラーが発生するケース
次のコードではエラーが発生し、プログラムは動きません。なぜでしょうか。
let subJohnny = johnny.dropFirst()
for (index, value) in subJohnny.enumerated() {
print(subJohnny[i])
}
Fatal error: Index out of bounds
使用しているメソッドの詳細と原因を順に解説していきます。
dropFirst()
dropFirst()
メソッドは指定された数の初期要素を除くすべてを含むSubSequence
を返すメソッドです。
今回の場合は0番目の要素『"J"』を除いたSubSequence
が返されました。
let subJohnny = johnny.dropFirst()
["O", "H", "N", "N", "Y", "🧑💻"]
SubSequence
SubSequence
は元のコレクションとインデックスを共有する特徴を持っています。
The subsequence shares indices with the original collection.
訳:subsequenceは元のコレクションとインデックスを共有する。
引用: Apple公式Doument- SubSequence
つまり、subJohnnyの始めの要素である『"O"』は0番目のインデックスではなく、1番目のインデックスということになります。言い換えればsubJohnnyには0番目のインデックスが存在しないとも言えます。
エラーが発生する理由
実はenumeratedのindexに当たる部分(前述のサンプルコードのindex
)は配列のインデックスではなく、単なる0から始まる連続した整数なのです。
Returns a sequence of pairs (n, x), where n represents a consecutive integer starting at zero and x represents an element of the sequence.
訳:nは0から始まる連続した整数、xはシーケンスの要素を表す。
引用: Apple公式Doument- enumerated()
その為,for文で初めに呼ばれるindexは整数0となります。
for (index, value) in subJohnny.enumerated() {
print(subJohnny[i])
}
しかし、前述の通りsubJohnnyには0番目のインデックス(『"J"』)が存在しません。
にも関わらず、0番目の要素にアクセスしてしまっています。
これが本ケースでエラーが発生した理由です。
エラーの解決策
上記エラーはenumerated()メソッドの代わりにzip()メソッドを使用することで解決できます。
for (index, value) in zip(subJohnny.indices, subJohnny) {
print(subJohnny[index])
}
indices
indices
プロパティはindex一覧を取得するプロパティです。
今回のケースでは、["O", "H", "N", "N", "Y", "🧑💻"]のインデックス(1..<7)を取得することができます。
zip
zipメソッドは指定した2つのシーケンスを基にペアのシーケンスを作成するメソッドです。
今回のケースではsubJohnnyの要素とindices
を掛け合わせることで期待する挙動を実装することができます。
考察
とは言え、正直なところ上記のように元のコレクションとインデックスを共有するようなケースはあまり遭遇しないような気もしました。(現役エンジニアの方に伺ってみたところ、同じようにおっしゃっていました。笑)
しかし、for文やforEachで要素のインデックスを扱う際はenumerated()メソッドではなく、zip()メソッドを使用することで、よりセーフティな実装ができるという点は知っておいて損はないなと筆者は感じました!
最後まで読んで頂きましてありがとうございました👍
参考
超感謝
本記事は@a-becoさんの以下の記事を大いに参考にさせて頂き、執筆いたしました。本記事の掲載を許可して頂き本当にありがとうございます!!!