概要
配列から、条件に当てはまったものを削除しようとした際、配列.eachで要素を一つ一つ取り出して、ifで検証してその要素をdeleteしようとしたのですが、以下のように失敗しました。
# 2つの配列 a, b において、 b に含まれている要素を a から取り除く
a = [*1..10] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b = [2, 3, 5, 7, 8]
a.each do |num|
if b.include?(num)
a.delete(num)
end
end
p a # => [1, 3, 4, 6, 8, 9, 10]
# 期待していた出力 => [1, 4, 6, 9, 10]
調べてみると以下の記事が参考になりました。
参考:配列の複数要素の削除はdelete_ifかreject、-なんかを使おう
https://qiita.com/zom/items/4461d786c35ae9eced0b
今回の場合、配列 a において、添字が 1 である数値 2 が削除された際に、その空いた添字 1 を埋めるように、配列 a の数値 3 以降の要素が左にズレることになり、元々添字が 2 であった数値 3 が添字 1 となり、イテレータ(繰り返し処理)の中で、数値 3 に対する処理が飛ばされてしまっていました。(数値 8 についても同様)
念の為、pメソッドを用いて、処理の途中で配列 a がどうなっているか確認しました。
a = [*1..10] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b = [2, 3, 5, 7, 8]
a.each do |num|
if b.include?(num)
a.delete(num)
p a
end
end
=> [1, 3, 4, 5, 6, 7, 8, 9, 10] # 数値 2 に対してdeleteが行われた直後、数値 3 が添字 1 に
=> [1, 3, 4, 6, 7, 8, 9, 10] # 数値 5 に対してdeleteが行われた直後、数値 6 が添字 3 に
=> [1, 3, 4, 6, 8, 9, 10] # 数値 7 に対してdeleteが行われた直後、数値 8 が添字 4 に
# 5回出力されるのを期待していたのが、3回しかされていないのは、数値 3 と 8 に対する処理が飛ばされたから
そこで、配列の中から条件に合った要素をまとめて削除する方法はないかと調べたところ、delete_ifとreject!というArrayクラスのメソッドがあったので、備忘録としてまとめました。
delete_if
delete_ifの機能は以下になります。
要素を順番にブロックに渡して評価し、その結果が真になった要素をすべて削除します。 delete_if は常に self を返します。
参考:Ruby 3.0.0 リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/method/Array/i/delete_if.html
each, if, deleteがひとまとめになったようなメソッドですね。
では使ってみます。
# 連続した数字を要素にもつ配列から偶数の要素を取り除く
numbers = [*1..10] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers.delete_if {|n| n.even?}
p numbers # => [1, 3, 5, 7, 9]
# 2つの配列 a, b において、 b に含まれている要素を a から取り除く
a = [*1..10] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b = [2, 3, 5, 7, 8]
a.delete_if do |num| # a の要素を順番にブロック変数に渡す
b.include?(num) # 配列 b に num があるかどうかを調べ、あった場合(trueのとき)、 a から num を削除する
end
p a # => [1, 4, 6, 9, 10]
要素をまとめて削除することができました。
reject!
基本的に機能はdelete_ifと変わりませんが、以下の部分が異なります。
delete_if は常に self を返しますが、reject! は要素が 1 つ以上削除されれば self を、 1 つも削除されなければ nil を返します。
参考:Ruby 3.0.0 リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/method/Array/i/delete_if.html
# 連続した数字を要素にもつ配列から偶数の要素を取り除く
numbers = [*1..10] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbers.reject! {|n| n.even?}
p numbers # => [1, 3, 5, 7, 9]
# 2つの配列 a, b において、 b に含まれている要素を a から取り除く
a = [*1..10] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b = [2, 3, 5, 7, 8]
a.reject! do |num|
b.include?(num)
end
p a # => [1, 4, 6, 9, 10]
# delete_if は常に self を返す
numbers = [1, 3, 5, 7, 9]
p numbers.delete_if {|n| n.even?} # => [1, 3, 5, 7, 9]
# reject! は要素が1つ以上削除されれば self を、1つも削除されなければ nil を返す
numbers = [1, 2, 3, 5, 7, 9]
p numbers.reject! {|n| n.even?} # => [1, 3, 5, 7, 9]
numbers = [1, 3, 5, 7, 9]
p numbers.reject! {|n| n.even?} # => nil
以上です。