1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

配列の要素をまとめて削除したい! delete_if, reject!を使う

Last updated at Posted at 2021-03-23

概要

配列から、条件に当てはまったものを削除しようとした際、配列.eachで要素を一つ一つ取り出して、ifで検証してその要素をdeleteしようとしたのですが、以下のように失敗しました。

Ruby
# 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 がどうなっているか確認しました。

Ruby
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_ifreject!という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がひとまとめになったようなメソッドですね。
では使ってみます。

Ruby
# 連続した数字を要素にもつ配列から偶数の要素を取り除く
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

Ruby
# 連続した数字を要素にもつ配列から偶数の要素を取り除く
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

以上です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?