LoginSignup
2
1

More than 5 years have passed since last update.

ある配列に単要素を加えた配列を破壊的変更を加えずに得る

Last updated at Posted at 2016-10-19

ある配列に要素を加えて新たに配列を得たいという場合、
pushするともとの値まで書き換わってしまいます。

a = [1, 2, 3]
b = 4

c = a << b
p c
# => [1, 2, 3, 4]
p a
# => [1, 2, 3, 4]

rubyのArrayメソッドのなかに破壊的変更を加えずに返すメソッドがあればいいのですが見当たりません。
https://ruby-doc.org/core-2.3.0/Array.html

以下に3つやり方を示します。
(@yanchaさん、@from_kyusyuさんにご教示いただきました。ありがとうございます。)

Array#+ を利用する

実はrubyの配列は+記号で簡単に結合することが出来ます。これが一番シンプルでメジャーなやり方のようです。

a = [1, 2, 3]
b = 4

c = a + [b] #Array#+を利用
p c
# => [1, 2, 3, 4]
p a
# => [1, 2, 3]

*を用いて変更元の配列を展開

2つ目は*を用いて配列を展開してしまう方法です。こちらもbを配列に入れる必要が無いので、良さそうです。

a = [1, 2, 3]
b = 4

c = [*a, b] #*で配列aを展開
p c
# => [1, 2, 3, 4]
p a
# => [1, 2, 3]

Array#dupを使う

dupを使って変更元の配列をコピーしてやるやり方もあります。ですが上記2つに比べるとやや冗長な感じがします。

a = [1, 2, 3]
b = 4

c = a.dup << b #dupでコピーした上でpushする
p c
# => [1, 2, 3, 4]
p a
# => [1, 2, 3]

番外編

論理和を利用する(※間違い)

自分がはじめに思いついた方法は論理和を取る方法でした。

a = [1, 2, 3]
b = 4

c = a | [b] # 論理和を取る
p c
# => [1, 2, 3, 4]
p a
# => [1, 2, 3]

しかしながらこのやり方では入れたい要素がすでに追加されている要素の場合は意図した動作になりません
(@taramさんにご教示いただきました。ありがとうございます。)

a = [1, 2, 3]
b = 2 # ここがすでにある値の場合

c = a | [b] # 論理和を取る
p c
# => [1, 2, 3] # 論理和を取っているので [1, 2, 3, 1] こうはならない
p a
# => [1, 2, 3]

結論

  • 加えたい値を単要素の配列にして+を用いて配列を結合する
  • *を用いて変更元の配列を展開する

のいずれかを用いればよいです。

ちなみに要素を削除したいという場合は-記号を用いれば良さそうです。

a = [1, 2, 3]
b = 2 # ここがすでにある値の場合

c = a - [b] # -記号で要素を削除
p c
# => [1, 3]
p a
# => [1, 2, 3]

とはいえ結構よくある操作なので、Ruby自体に破壊的変更を加えずに単要素を追加、削除できるメソッドがあればよいのになと感じました。
また、今回投稿していろんな方にコメント頂いたことで間違いに気づくことができた上に正しいやり方も教えていただいてとても勉強になりました。
自分のようなエンジニアの社員1人の孤高系エンジニアにはQiitaはとてもいいサービスです!

追記

RailsではHashで同様のことをするためにHash#exceptというモンキーパッチのメソッドがあるようです。

class Hash
  # Returns a hash that includes everything but the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false, c: nil}
  #
  # This is useful for limiting a set of parameters to everything but a few known toggles:
  #   @person.update(params[:person].except(:admin))
  def except(*keys)
    dup.except!(*keys)
  end

  # Replaces the hash without the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except!(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false }
  def except!(*keys)
    keys.each { |key| delete(key) }
    self
  end
end

なぜかArray#exceptはないのですが、Hashを真似して書くとこんな感じになります

class Array

  def except(*values)
    dup.except!(*values)
  end

  def except!(*values) 
    values.each {|v| delete(v)}
    self
  end

end

Hashに入れるならArrayにも入れてほしいですね。

2
1
5

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
2
1