【Ruby】「!」が付かない破壊的メソッドまとめ

#概要
Rubyのインスタンスメソッドの内、オブジェクト自身の内容を変えてしまうものは一般的に破壊的メソッドと呼ばれます。

```ruby
##非破壊的メソッド「Array#reject」
arry = [1,2,3]
p arry.reject {|item| item == 2} #=> [1, 3]
p arry #=> [1, 2, 3]

##破壊的メソッド「Array#delete_if」
arry = [1,2,3]
p arry.delete_if {|item| item == 2} #=> [1, 3]
p arry #=> [1, 3]
```
上記の例では「非破壊的メソッド」である`Arrary#reject`を用いた場合には`arry`そのものの値は変更していないが、「破壊的メソッド」である`Array#delete_if`を用いた場合には`arry`そのものの値が変更されている事がわかります。

Rubyでは多くの破壊的メソッドの後には「！」が付いていますが、付いていないものも存在します。
今回はその「！」を末尾に取らずとも破壊的メソッドの役割を果たすメソッドをクラス別・またメソッドの目的別にわけてまとめました。

#Arrayクラス
###配列に要素を追加する。
- `Array#<<` :末尾に引数で指定された要素を追加します。
- `Array#concat` :末尾に引数で指定された別のarrayを結合します。
- `Array#insert`:第一引数で指定した場所に第二引数で指定した要素を加えます。
- `Array#push`/`Array#append(2.5以降)`: Arrayの末尾に要素を追加します。
- `Array#unshift`/`Array#prepend(2.5以降)`:Arrayの先頭に要素を追加します。

```ruby
arry = Array.new
p arry << 'ele1' #=> ["ele1"]
p arry.concat(['ele2']) #=> ["ele1", "ele2"]
p arry.insert(1, 'ele3') #=> ["ele1", "ele3", "ele2"]
p arry.push('ele4') #Arrayの末尾に要素を追加します。#=> ["ele1", "ele3", "ele2", "ele4"]
p arry.unshift('ele5') #Arrayの先頭に要素を追加します。 #=>["ele5", "ele1", "ele3", "ele2", "ele4"]
```

###配列の要素を変更する。
- `Array#[]=`:[]内で指定した位置の要素を右辺で置き換えます。
- `Array#fill`:arrayを引数で指定した要素で埋め尽くします。
- `Array#replace`: 引数で指定したarrayと置き換えます。
- `Array#keep_if`:要素の数だけ繰り返しブロックを実行し、ブロックの戻り値が真になった要素を残し、偽になった要素を削除します。

```ruby
arry = Array.new
p arry << 'ele1' #=> ["ele1"]

arry[0] = 'ele0'
p arry #=> ["ele0"]
p arry.fill('ele') #=> ["ele", "ele"]
p arry.replace(['ele1', 'ele2']) #=> ["ele1", "ele2"]

arry.keep_if {|ele| ele == 'ele1'}
p arry #=> ['ele1']
```

###配列の要素を削除する。
- `Array#delete`:引数で指定した要素をarray内から削除します。
- `Array#delete_at`: 引数で指定したインデックスに該当する要素をarray内から削除します。
- `Array#delete_if(= reject!)`:ブロックで渡した条件に該当する要素をarray内から削除します。
- `Array#clear`:array内から全ての要素を削除します。
- `Array#shift`:arrayの先頭の要素を削除します。
- `Array#pop`:arrayの末尾の要素を削除します。

```ruby
arry = Array.new
arry.concat(['ele0', 'ele1', 'ele2', 'ele3'])

arry.delete('ele0')
p arry #=> ["ele1", "ele2", "ele3"]

arry.delete_at(0)
p arry #=> ["ele2", "ele3"]

arry.delete_if { |a| a == 'ele2' }
p arry #=> ["ele3"]

arry.clear
p arry #=> []


arry2 = ['ele0', 'ele1', 'ele2']

arry2.shift
p arry2 #=> ["ele1", "ele2"]

arry2.pop
p arry2 #=> ["ele1"]
```

#Stringクラス
###文字列を変更する。
- `String#[]=` :[]内で指定した位置の要素を右辺で置き換えます。
- `String#insert`:第一引数で指定した場所に第二引数で指定した要素を加えます。
 
```ruby
str = 'string'
p str.insert(0, 'S') #=> "Sstring"

str[1] = 'S'
p str #=> #=> "SString"
```

###文字列を置き換える。
- `String#replace`:引数で指定した要素と置き換えます。
- `String#setbyte`:第一引数で指定されたインデックスにある要素のバイト数を置き換える。

```ruby
str = 'string'
p str.replace('String2') #=> "String2"

str.setbyte(0, 0x41)
p str #=> "Atring2" #「A」はアスキーコードで「0x41」
```

###文字列を連結する。
- `String#<<`:文字列の末尾に引数で指定された要素を追加します。
- `String#concat`:文字列の末尾に引数で指定された要素を追加します。
- `String#prepend`:文字列の先頭に引数で指定された要素を追加します。

```ruby
str = 'string'
p str << '3' #=> "string3"
p str.concat('4') #=> "string34"
p str.prepend('Hello ') #=> "Hello string34"
```

###文字列を削除する。

- `String#clear`:文字列を空文字列に変えます。

```ruby
string = 'String'

p string.clear #=> ""
```

**deleteメソッドはArrayやHashクラスでは破壊的メソッドだが、Stringクラスでは非破壊的メソッド扱いとなります。**

#Hashクラス
###ハッシュを変更する。
- `Hash#[]=`:ハッシュにキーkeyと値valのペアを追加します。keyのキーがすでにあれば、そのキーの値をvalに変更します。
- `Hash#store`:ハッシュにキーkeyと値valのペアを追加します。keyのキーがすでにあれば、そのキーの値をvalに変更します。(`Hash#[]=`のエイリアス)
- `Hash#replace`:ハッシュの内容を引数で与えられたhashと置き換えます。
- `Hash#update(=merge!)`:レシーバhashの末尾に引数で与えられたhashの内容を加えます。
- `Hash#keep_if`:繰り返しブロックを実行し、ブロックの戻り値が真になったキーと値を残し、偽になったものを削除します。

```ruby
hash = {first_name: 'Nichiro', last_name: 'Suzuki'}
hash[:first_name] = 'Sanchiro'
p hash #=> {:first_name=>"Sanchiro", :last_name=>"Suzuki"}

hash[:team] = 'NYY'
p hash #=>{:first_name=>"Sanchiro", :last_name=>"Suzuki", :team=>"NYY"}

hash.store(:team, 'LAA')
p hash #=> {:first_name=>"Sanchiro", :last_name=>"Suzuki", :team=>"LAA"}

p hash.replace({first_name: 'Yochiro', last_name: 'Suzuki'}) #=> {:first_name=>"Yochiro", :last_name=>"Suzuki"}

p hash.update({'team' => 'LAA'}) #=> {:first_name=>"Yochiro", :last_name=>"Suzuki", "team"=>"LAA"}

hash.keep_if { |key, val| key == :first_name }
p hash #=> {:first_name=>"Yochiro"}
```

###ハッシュの値を削除する。
- `Hash#delete`:ハッシュから引数keyと同じキーを探して、キーと値を削除します。
- `Hash#delete_if(= reject!)`:与えられたブロックの戻り値が真になったキーと値を削除します。(ブロック引数key、valにはキーと値が入ります。)
- `Hash#shift`:ハッシュの先頭からキーと値を1組削除します。(削除したキーと値を配列にして返します。)
- `Hash#clear`:キーと値をすべて削除し、ハッシュを空にします。

```ruby
hash = {first_name: 'Nichiro', last_name: 'Suzuki'}
hash.delete(:first_name)
p hash #=> {:last_name=>"Suzuki"}

scores = { "Alice" => 50, "Bob" => 60, "Carol" => 90, "David" => 40 }
scores.delete_if { |key, val| val < 50 }
p scores #=> {"Alice"=>50, "Bob"=>60, "Carol"=>90}

hash2 = {first_name: 'Nichiro', last_name: 'Suzuki'}
p hash2.shift #=>[:first_name, "Nichiro"]
p hash2 #=>{:last_name=>"Suzuki"}

p hash2.clear #=> {}
```

各種メソッドの動き方の詳細等々は以下の公式レファレンス等での確認をよろしくお願いいたします。
[Ruby リファレンスマニュアル](https://docs.ruby-lang.org/ja/2.5.0/doc/index.html)


# 追記(2021/06/15)
@jnchitoさんにこの件に関してMatzさんの設計思想を[本記事のコメント欄](https://qiita.com/k-penguin-sato/items/ab2267ca99d5aedcc40a#comment-34fc86c9b210fa83bd85)で記載して頂きました。

`"!"が付く・付かない＝破壊的・非破壊的`ではなく、Rubyにおける`!`はあくまでも`!`のつかないメソッドよりも「危険」であることを示す目印のようなものとなります。

以下にコメントにて共有頂いたMatzさんのTweetと本件に関して参考になる記事を記載させて頂きます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Rubyの「！」つきメソッドは、「！」がついていないメソッドよりもより「危険」という意味です。<br>更に exit! のようにレシーバーを破壊しないが(exitより)危険というメソッドもあります。 <a href="https://t.co/JmurlrZIxM">https://t.co/JmurlrZIxM</a></p>&mdash; Yukihiro Matsumoto (@yukihiro_matz) <a href="https://twitter.com/yukihiro_matz/status/1310437519280939008?ref_src=twsrc%5Etfw">September 28, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- [破壊的メソッド名には!をつけなければならない…なんて、誰が言ったの？](https://qiita.com/tadsan/items/7baab2605a4d8ac1858e)