each で配列を回しながら delete すると要素が飛ばされる
eachで配列を反復しながらdelete_atやdeleteで要素を取り除くと、後続の要素が前に詰まってインデックスがずれ、次に処理されるはずだった要素が読み飛ばされます。
なぜエラーが出ないのか
出力: [4, 3, 5]
(エラーなし)- Rubyは何も報告しません。文法として正しいためです
出力: [4, 3, 5]- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 nums = [2, 4, 6, 3, 8, 5] 2 nums .each do |n| ^ 3 nums.delete(n) if n.even? 4 end 5 puts nums.inspect
出力: [4, 3, 5]
元の配列を直接eachで回しながらdeleteすると、削除によるずれで次の要素が読み飛ばされ、4のような偶数が消えずに残ります。
直し方: nums を nums.dup にします。
広告
広告スロット(未設定)
パターン2
1 vals = [4, 2, 8, 1, 6, 3] 2 vals .each do |n| ^ 3 vals.delete(n) if n.even? 4 end 5 puts vals.inspect
出力: [2, 1, 3]
反復中の配列そのものを変更すると、インデックスがずれて意図しない要素が生き残ります。
直し方: vals を vals.dup にします。
パターン3
1 data = [6, 2, 4, 9, 8, 1] 2 data .each do |n| ^ 3 data.delete(n) if n.even? 4 end 5 puts data.inspect
出力: [2, 9, 1]
安全に取り除きたい場合は、複製した配列を反復しながら元の配列を変更するようにします。
直し方: data を data.dup にします。
パターン4
1 items = [8, 4, 2, 5, 6, 7] 2 items .each do |n| ^ 3 items.delete(n) if n.even? 4 end 5 puts items.inspect
出力: [4, 5, 7]
eachは削除によって縮んだ配列を考慮せず、内部のインデックスを機械的に進めるだけです。
直し方: items を items.dup にします。
パターン5
1 queue = [2, 6, 4, 1, 8, 9] 2 queue .each do |n| ^ 3 queue.delete(n) if n.even? 4 end 5 puts queue.inspect
出力: [6, 1, 9]
dupで複製したスナップショットを反復すれば、元の配列をいくら変更してもずれの影響を受けません。
直し方: queue を queue.dup にします。
よくある誤解
「削除した分だけ後ろにずれても、eachが自動で調整してくれるはず」という思い込みは誤りです。eachは内部でインデックスを進めているだけなので、削除でずれた要素は無条件に読み飛ばされます。反復中に安全に取り除きたい場合はselectやreject(または元の配列を複製してから回す)を使う必要があります。
まとめ
each で配列を回しながら delete すると要素が飛ばされるは中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。