Rubyのシンタックス勉強用（シンボルとハッシュ 編）

Rubyのシンタックスを息を吸うように書くために、少しでも理解が怪しいシンタックスを繰り返したくために書きます。

シンボル編とハッシュ編があるので、基礎がないならこちらも見たほうがいいです。

- [Rubyのシンタックス勉強用（シンボル 編） \- Qiita](https://qiita.com/ryosuketter/items/045f09712d33bdeb1688)
- [Rubyのシンタックス勉強用（ハッシュ 編） \- Qiita](https://qiita.com/ryosuketter/items/dd70f24e0ca34e1a694a)

# 前提知識

- ハッシュの`key`は、同じデータ型にする必要はない。混乱の原因なので基本は揃えたほうがいい
- ただ、`value`は異なるデータ型が入る可能性がある

# メソッドのキーワード引数とハッシュ

例えば、以下のようなメソッドがあったとしよう。

Harder かつ Better かつ Faster かつ Stronger であれば以下URLにある曲がリリースできる`daftpunk`メソッドがあったとしよう。

https://youtu.be/GDpmVUEjagg

```rb
def daftpunk(hard, good, fast, strong)
  if hard && good && fast && strong
    puts 'release'
  else
    puts 'not release'
  end
end

daftpunk(true,true,true,true)
=> release
```

メソッドの引数の中身を見れば、どんな意図のメソッドかがわかるけど、以下のメソッドの呼び出しだけ見たら、よくわからないかと思います。

```rb
daftpunk(true,true,true,false)
=> not release
```

そんな時に使うのがキーワード引数というやつです。

引数が`true`,`false`だけの時よりも、だいぶ可読性がいいのではないでしょうか？

キーワード引数を持つメソッドを呼び出す場合は、ハッシュを作成した時と同じく`シンボルkey: value`の形式で引数を指定します。

```rb
def daftpunk(harder: true, better: true, faster: true, stronger: true)
  if harder && better && faster && stronger
    puts 'release'
  else
    puts 'not release'
  end
end


daftpunk(harder: true, better: true, faster: true, stronger: true)
=> release

# デフォルト引数は省略可能
daftpunk
=> release

# デフォルト引数は順番を入れ替えることも可能
daftpunk(better: true, faster: true, stronger: true, harder: false)
=> not release

# 未定義なキーワード引数を指定した場合はエラーになります
daftpunk(one_more_time: true)
=> 'daftpunk': unknown keyword: one_more_time (ArgumentError)
```

デフォルト引数の値がない場合、メソッド呼び出す際に値を明示しないとエラーになります。

```rb
def daftpunk(harder: true, better: true, faster: true, stronger:)
  if harder && better && faster && stronger
    puts 'release'
  else
    puts 'not release'
  end
end

daftpunk(harder: true, better: true, faster: true, stronger: true)
=> release

daftpunk(harder: true, better: true, faster: true)
=> 'daftpunk': missing keyword: stronger (ArgumentError)
```

メソッドを呼び出す側の引数は、キーワード引数に一致するハッシュを変数に格納して、引数として渡すこともできます。

```rb
params = { harder: true, better: true, faster: true, stronger:true }

def daftpunk(harder: true, better: true, faster: true, stronger:true)
  if harder && better && faster && stronger
    puts 'release'
  else
    puts 'not release'
  end
end

daftpunk(params)
=> release
```

参考: [プロを目指す人のためのRuby入門_156p](https://ruby-book.jnito.com/)

# ハッシュでよく使うメソッド

基本的に使うハッシュは以下。

```ruby:Hash_Sample
languages = { ruby: 'rails', php: 'laravel', python: 'django', }
```

## 問題1

Hash_Sampleのキーだけを配列で返してください。

## 正解1

```rb
languages = { ruby: 'rails', php: 'laravel', python: 'django', }

p languages.keys
=> [:ruby, :php, :python]
```

参考: [プロを目指す人のためのRuby入門_162p](https://ruby-book.jnito.com/)

## 問題2

Hash_Sampleのキーだけを配列で返してください。

## 正解2

```rb
languages = { ruby: 'rails', php: 'laravel', python: 'django', }

p languages.values
=> ["rails", "laravel", "django"]
```

参考: [プロを目指す人のためのRuby入門_162p](https://ruby-book.jnito.com/)

## 問題3

特定のキーを持っているかどうか。戻り値が `TRUE` / `FALSE` なメソッドを書いてください。
エイリアスメソッド合わせて4つあります。

## 正解3

```rb
languages = { ruby: 'rails', php: 'laravel', python: 'django', }

p languages.has_key?(:ruby)
p languages.key?(:php)
p languages.include?(:python)
p languages.member?(:javascript)

=> true
=> true
=> true
=> false
```

参考: [プロを目指す人のためのRuby入門_162p](https://ruby-book.jnito.com/)

## 問題4

ハッシュ（変数`languages`）の中でハッシュ（変数`others`）を展開させてください。

展開させるハッシュは以下

```
others = { javascript: 'react', java: 'struts' }
```

## 正解4

```rb
others = { javascript: 'react', java: 'struts' }
languages = { ruby: 'rails', php: 'laravel', python: 'django', **others }
p languages

=> {:ruby=>"rails", :php=>"laravel", :python=>"django", :javascript=>"react", :java=>"struts"}
```

参考: [プロを目指す人のためのRuby入門_163p](https://ruby-book.jnito.com/)

## 問題5

擬似キーワード引数

```
others = { javascript: 'react', java: 'struts' }
```

## 正解5

```rb
others = { javascript: 'react', java: 'struts' }
languages = { ruby: 'rails', php: 'laravel', python: 'django', **others }
p languages

=> {:ruby=>"rails", :php=>"laravel", :python=>"django", :javascript=>"react", :java=>"struts"}
```

参考: [プロを目指す人のためのRuby入門_163p](https://ruby-book.jnito.com/)

## 問題6

ハンバーガーの注文系のメソッド`order_hamburger`メソッドの引数に、 

店内で食べるか（`For_Here`）、外で食べるか（`To_Go`）を指定して、

それ以外は、擬似キーワード引数にして、トマトやチーズのトッピングが必要か不要かをメソッド呼び出しで指定できるようにしてください。

擬似キーワード引数の参考 （[プロを目指す人のためのRuby入門_163p](https://ruby-book.jnito.com/)）

return 例

```
"For_Here"
"with ONION"
"with TOMATO"
"with CHEESE"
```

## 正解6

```rb
def order_hamburger( where, toppings={} )
  p "#{where}"
  p 'with ONION' if toppings.key?(:onion)
  p 'with TOMATO' if toppings.key?(:tomato)
  p 'with CHEESE' if toppings.key?(:cheese)
end

order_hamburger( 'For_Here', onion: true, tomato: true, cheese: true, )
```

キーワード引数を使う場合と比較すると、存在しない`key`を指定するとエラーが発生するが、類似キーワード引数は、ハッシュなので、どれだけ渡してもエラーが起こりません。
ただ、お仕事での実装は、普通のキーワード引数を使った方がよさそうです。

[プロを目指す人のためのRuby入門_164p](https://ruby-book.jnito.com/)

## 問題7

キーワード引数に定義されていないキーワードを渡すとエラーになります。

```rb
def order_hamburger( where, onion: true, tomato: true )
  p "eat at #{where}"
end

order_hamburger( 'For_Here', onion: true, tomato: true, cheese: true, )

=> 'order_hamburger': unknown keyword: cheese (ArgumentError)
```

これが、どんなキーワードでも受け入れてくれるように書いてください。キーワード（`cheese`）を受け入れてください。

return 例

```
"eat at For_Here {:onion=>true, :tomato=>true, :cheese=>true}"
```

## 正解7

```rb
def order_hamburger( where, **toppings )
  p "eat at #{where} #{toppings}"
end

order_hamburger( 'For_Here', onion: true, tomato: true, cheese: true, )
```

[プロを目指す人のためのRuby入門_164p](https://ruby-book.jnito.com/)

# ハッシュから配列へ変換、配列からハッシュ変換

Hashオブジェクトに対して、`to_a`メソッドで配列に変換できます。その際、`key`と`value`の組み合わせが1つの配列になり、その配列が複数の要素としてあり、それをさらに配列としてくくっている多次元な配列になります。

```rb
languages = { ruby: 'rails', php: 'laravel', python: 'django', }

p languages.to_a
=> [[:ruby, "rails"], [:php, "laravel"], [:python, "django"]]
```

逆に、Arrayオブジェクトに対して、`to_h`メソッドで配列をハッシュに変換できます。

```rb
languages = { ruby: 'rails', php: 'laravel', python: 'django', }

p languages.to_a
=> [[:ruby, "rails"], [:php, "laravel"], [:python, "django"]]

p languages.to_h
=> {:ruby=>"rails", :php=>"laravel", :python=>"django"}
```

`to_h`メソッドで配列をハッシュに変換できますが、`key`と`value`の組み合わせが1つの配列になっていて、その配列が複数の要素としてなどの形式が揃っていないとエラーになります。

```rb
langs = ['ruby', 'rails', 'php', 'laravel', 'python', 'django']

p langs.to_h
=> 'to_h': wrong element type String at 0 (expected array) (TypeError)
```

`to_h`メソッドで変換できても、`key`が重複した場合は、最後の要素がハッシュの要素になります。`key`重複はない方が望ましいです。

```rb
langs = [[:ruby, "rails"], [:php, "laravel"], [:python, "django"], [:ruby, "ruby on rails"]]

p langs.to_h
=> {:ruby=>"ruby on rails", :php=>"laravel", :python=>"django"}
# [:ruby, "rails"] の要素が亡くなっていますね
```

[プロを目指す人のためのRuby入門_167p](https://ruby-book.jnito.com/)

# ハッシュの値

ハッシュに、存在しない`key`を指定しても`nil`が返ります。

```rb
hash = {}
p hash[:test] # => nil
```

`nil`、以外の値を返したい場合は、`Hash.new()`の引数に初期値を設定すればできます。

```rb
languages = Hash.new('something')
p languages[:ruby]   # => "something"
p languages[:php]    # => "something"
p languages[:python] # => "something"
p languages[:ruby].object_id   # => 70138707695000
p languages[:php].object_id    # => 70138707695000
p languages[:python].object_id # => 70138707695000
```

ただし、注意が必要なのは、上を見てもわかる通り、何回呼び出しても同じ初期値が返ります。なので、初期値に対して、破壊的な変更をすると、呼び出してるやつ全部に影響があります。

その問題を解決する方法としては、ブロック引数として初期値を設定すると、ここの要素はいミュータブルなオブジェクトになります。

```rb
languages = Hash.new { 'something' }
p languages[:ruby]   # => "something"
p languages[:php]    # => "something"
p languages[:python] # => "something"
p languages[:ruby].object_id   # => 70174736763180
p languages[:php].object_id    # => 70174736762900
p languages[:python].object_id # => 70174736763180
```

[プロを目指す人のためのRuby入門_168p](https://ruby-book.jnito.com/)

# シンボルを作成する方法

## エラーになるシンボルの作成方法

こんな場合はエラーになります。

- 数字で始まる
- ハイフンがある
- スペースがある

```rb
p :123           # => syntax error
p :ruby-on-rails # => syntax error
p :ruby on rails # => syntax error
```

上記も、シングルクオートで囲むとエラーになりません。シンボルとして認識されます。

```rb
p :'123'
p :'ruby-on-rails'
p :'ruby on rails'
```

## %記法でシンボルを複数宣言

```rb
p %i(ruby php python)
=> [:ruby, :php, :python]
```

[プロを目指す人のためのRuby入門_170p](https://ruby-book.jnito.com/)