Code Fix

中級

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のような偶数が消えずに残ります。

直し方: numsnums.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]

反復中の配列そのものを変更すると、インデックスがずれて意図しない要素が生き残ります。

直し方: valsvals.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]

安全に取り除きたい場合は、複製した配列を反復しながら元の配列を変更するようにします。

直し方: datadata.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は削除によって縮んだ配列を考慮せず、内部のインデックスを機械的に進めるだけです。

直し方: itemsitems.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で複製したスナップショットを反復すれば、元の配列をいくら変更してもずれの影響を受けません。

直し方: queuequeue.dup にします。

この問題を解いてみる →

よくある誤解

「削除した分だけ後ろにずれても、eachが自動で調整してくれるはず」という思い込みは誤りです。eachは内部でインデックスを進めているだけなので、削除でずれた要素は無条件に読み飛ばされます。反復中に安全に取り除きたい場合はselectやreject(または元の配列を複製してから回す)を使う必要があります。

まとめ

each で配列を回しながら delete すると要素が飛ばされるは中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)