ある配列に要素を加えて新たに配列を得たいという場合、
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にも入れてほしいですね。