読者です 読者をやめる 読者になる 読者になる

ハイパーニートプログラマーへの道

頑張ったり頑張らなかったり

【Ruby】配列の要素を順に削除するには、eachで回してdeleteすればいいじゃん、と思っていた時期が私にもありました

配列の要素を順番に削除していきたいなあ、と思いまして。 eachで回せばいいじゃんと。
こんな感じで

# encoding: utf-8

numbers = [1, 2, 3, 4, 5, 6, 7]
numbers.each { |number|
  p numbers
  numbers.delete(number)
}
p numbers

そしたら・・・

[1, 2, 3, 4, 5, 6, 7]
[2, 3, 4, 5, 6, 7]
[2, 4, 5, 6, 7]
[2, 4, 6, 7]
[2, 4, 6]

!?

アイエエエエ! ナンデ!?
2、4、6が残ってる。要は1、3、5、7しか削除できなかったってこと?

ザッケンナコラー!
なんじゃこりゃMatzふざくんな、と。島根燃やすぞ(ついでに鳥取も)、とフンガーしたわけですが・・・。

よく考えれば、分かることかもしれませんが、イテレートしている最中に要素を削除することで内部ポインタがずれてしまうのです。

【Ruby】配列の複数要素の削除はdelete_ifかrejectを使おう | one's world

あ・・・デスヨネーwww

はじめは配列の[0]番目 = 1になっていますが、deleteしたことにより[0]番目 = 2になるんですよね。
そしてeachの先頭に戻ると「今度は[1]番目を見ていくよー」となるので、[1]番目 = 3を削除してしまうと。

なのでdelete_ifを使い

delete_ifメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値が真になった要素を削除します。レシーバ自身を変更するメソッドです。ブロック引数itemには各要素が入ります。

delete_if (Array) - Rubyリファレンス

# encoding: utf-8

numbers = [1, 2, 3, 4, 5, 6, 7]
numbers.delete_if { |number| #ブロック内の戻り値が真ならその要素を削除
  p numbers #ループするごとに1個ずつ削除されている
  !numbers.empty? #配列が空でないなら真
}
p numbers #最終的には空
[1, 2, 3, 4, 5, 6, 7]
[2, 3, 4, 5, 6, 7]
[3, 4, 5, 6, 7]
[4, 5, 6, 7]
[5, 6, 7]
[6, 7]
[7]
[]

はーすっきり。
そもそも配列がなんたるかを理解していなかった私が悪かったわけですな・・・。

Matzさんwwwサーセンwww Ruby最高っすwww