ハッシュとは
一言で言えば、「インデックス(添字)として、整数(スカラー値)以外のデータ型を用いる事ができる配列」のことです。
Rubyでは「ハッシュ」という語が用いられていますが、以下のような別名も存在します。
- 連想配列(Perlなど)
- マップ(Javascriptなど)
- 辞書(Pythonなど)
ハッシュを定義するには
以下は各要素を角カッコ[]
を使って定義する方法です。
>> user = {} # {}は空のハッシュ
=> {}
>> user["first_name"] = "Michael" # キー…"first_name"、値…"Michael"
=> "Michael"
>> user["last_name"] = "Hartl" # キー…"last_name"、値…"Hartl"
=> "Hartl"
>> user["first_name"] # ハッシュの要素へは、配列の要素と類似する方法でアクセスできる
=> "Michael"
>> user # ハッシュのリテラル表記
=> {"first_name"=>"Michael", "last_name"=>"Hartl"}
以下はハッシュロケット=>
を使って定義する方法です。角カッコを使った定義よりも簡単な書き方です。Rubyにおいては、ハッシュの最初と最後にスペースを入れる記法が好まれているそうです。
>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"first_name"=>"Michael", "last_name"=>"Hartl"}
シンボル
シンボルとは、ソースコード上の文字列と内部処理に使う一意の数値を1対1に割り付けたものです。「シンボルとは何かを端的に説明するとどうなるか」というテーマについて、先人は以下のように言及しています。
参考記事:Rubyの文字列とシンボルの違いをキッチリ説明できる人になりたい
>> :foobar.object_id
=> 7768988
>> a = :foobar
=> :foobar # => 7768988
>> b = :foobar
=> :foobar # => 7768988
>> a.equal?(b)
=> true
>> a = "foobar"
=> "foobar" # => 47206601248540
>> b = "foobar"
=> "foobar" # => 47206601227140
>> a.equal?(b)
=> false
文字列式は、同じ文字列リテラルを指すものであっても、評価されるたびに違うオブジェクトを生成します。一方で、同じシンボルに対しては、何度評価されても常に同じオブジェクトを返します。その違いは、equal?
メソッドの戻り値の違いにも現れています。
ハッシュのキーとしてシンボルを用いる
>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name] # :nameというキーに対応する値
=> "Michael Hartl"
>> user[:password] # 未定義のキーに対する値
=> nil
>> user["password"]
=> nil
Rubyのハッシュでは、一般に、キーとしてシンボルを用います(「名前という概念を定義するための存在」というシンボルの存在意義からして自然な成り行きですよね)。定義済みのハッシュの未定義のキーに対する値を参照しに行った場合、nil
を返します。
>> user = { name: "Michael Hartl", email: "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
実用的には、上記のような「キーを表すシンボルとハッシュロケットの組を、シンボル名とコロンに置き換えた表記」も広く用いられます。Pythonなどで見る表記に似ていますね。
ハッシュのハッシュ
ハッシュの値として、配列や別のハッシュを与えることも可能です。Webレスポンス、JSON、YAMLなどを扱う場合に頻出するデータ構造です。Railsチュートリアル 第4章では、「ハッシュのハッシュ」を扱っています。
>> params = {} # 空のハッシュを定義する
=> {}
>> params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
=> {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
>> params
=> {:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
>> params[:user][:email]
=> "mhartl@example.com"
ハッシュに対してeach
メソッドを実行し、その結果をブロックで受ける
以下は、ハッシュに対しeach
メソッドを実行し、その結果をブロックで受けて実行する例です。
>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
>> flash.each do |key, value|
?> puts "Key #{key.inspect} has value #{value.inspect}"
>> end
Key :success has value "It worked!"
Key :danger has value "It failed."
=> {:success=>"It worked!", :danger=>"It failed."}
ハッシュに対するeach
メソッドをブロックで受ける場合、ブロックの変数が2つあることがポイントですね。キーに対応する変数と、値に対応する変数の2つです。ハッシュに対するeach
メソッドは、「キーと値のペア」を単位として行われるのです。
inspect
メソッドと、モジュール関数p
inspect
メソッドは、オブジェクトを人間が読める形式に変換して返すメソッドです。「人間が読める形式」が何を指すかは、オブジェクトの型によって様々です。
>> puts (1..5).to_a.inspect
[1, 2, 3, 4, 5]
=> nil
>> puts :name, :name.inspect
name
:name
=> nil
>> puts "It worked!", "It worked!".inspect
It worked!
"It worked!"
=> nil
puts Time.new.inspect
2019-09-12 13:26:36 +0000
=> nil
inspect
メソッドと関係して、p
というモジュール関数1 2が存在します。Rubyリファレンスマニュアルによれば、「引数のinspect
メソッドの返り値と改行を順番に出力します」とのことです。戻り値がnilであるputs
とは異なり、p
の戻り値は「引数のオブジェクト」となります。以下、p
の実行例です。
>> p :name
:name
=> :name
> p "It worked!"
"It worked!"
=> "It worked!"
>> p Time.new
2019-09-12 13:29:48 +0000
=> 2019-09-12 13:29:48 +0000
演習 - ハッシュとシンボル
1.1 キーが’one’、’two’、’three’となっていて、それぞれの値が’uno’、’dos’、’tres’となっているハッシュを作ってみてください。
ハッシュロケットを使った記法でハッシュを作ってみます。
>> es = { 'one' => 'uno', 'two' => 'dos', 'three' => 'tres' }
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
>> p es
{"one"=>"uno", "two"=>"dos", "three"=>"tres"}
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
ハッシュの変数名はesとしてみました。
1.2 その後、ハッシュの各要素をみて、それぞれのキーと値を"’#{key}’のスペイン語は’#{value}’"といった形で出力してみてください。
1.1の続きです。each
メソッドとブロック記法を用います。Rails Consoleで日本語入力が可能な環境を整備するのが面倒だったので、メッセージは英語です。
>> es.each do |eng, esp|
?> p "#{eng} is #{esp} in Spanish."
>> end
"one is uno in Spanish."
"two is dos in Spanish."
"three is tres in Spanish."
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
2.1 person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値 (名前など) を入力してください。
>> person1 = { :first => "Michael", :last => "Hartl" }
=> {:first=>"Michael", :last=>"Hartl"}
>> person2 = { :first => "Foo", :last => "Bar" }
=> {:first=>"Foo", :last=>"Bar"}
>> person3 = { first: "Hoge", last: "Fuga" }
=> {:first=>"Hoge", :last=>"Fuga"}
person3のみ、省略記法を用いて定義してみました。
2.2 その後、次のようなparamsというハッシュのハッシュを作ってみてください。
1.) キーparams[:father]の値にperson1を代入
2). キーparams[:mother]の値にperson2を代入
3). キーparams[:child]の値にperson3を代入
最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)
>> params = { father: person1, mother: person2, child: person3 }
=> {:father=>{:first=>"Michael", :last=>"Hartl"}, :mother=>{:first=>"Foo", :last=>"Bar"}, :child=>{:first=>"Hoge", :last=>"Fuga"}}
>> params[:father][:first].equal?(person1[:first])
=> true # => 47206604256140
>> params[:child][:last].equal?(person3[:last])
=> true # => 47206604128880
- params[:father][:first]とperson1[:first]
- params[:child][:last]とperson3[:last]
以上はオブジェクトとしても一致しています。
3. userというハッシュを定義してみてください。このハッシュは3つのキー:name、:email、:password_digestを持っていて、それぞれの値にあなたの名前、あなたのメールアドレス、そして16文字からなるランダムな文字列が代入されています。
今回は、空のハッシュから順を追って定義してみることにします。
>> user = {}
=> {}
>> user[:name] = 'Foo bar'
=> "Foo bar"
>> user[:email] = 'example@example.jp'
=> "example@example.jp"
>> user[:password_digest] = [*'0'..'9', *'a'..'z', *'A'..'Z'].shuffle[0..15].join
=> "DjaIs4nV1Jh6OYKt"
>> p user
{:name=>"Foo bar", :email=>"example@example.jp", :password_digest=>"DjaIs4nV1Jh6OYKt"}
=> {:name=>"Foo bar", :email=>"example@example.jp", :password_digest=>"DjaIs4nV1Jh6OYKt"}
「16文字からなるランダムな文字列」というお題を出されたので、[*'0'..'9', *'a'..'z', *'A'..'Z'].shuffle[0..15].join
という実装をしてみました。これは、「英大文字・英小文字・数字各1文字ずつから成る配列をシャッフルした上で、先頭15要素を取り出し、連結して文字列にしたものを返す」というメソッドチェーンです。
なお、例えば[*'a'..'z']
は、内部的には('a'..'z').to_a
を呼び出すことになります。
4. Ruby API (訳注: もしくはるりまサーチ) を使って、Hash
クラスのmerge
メソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。
演習コード{ "a" => 100, "b" => 200 }.merge({ "b" => 300 })
以下の記述がポイントですね。
self と others に同じキーがあった場合はブロック付きか否かで 判定方法が違います。ブロック付きのときはブロックを呼び出してその返す値を重複キーに対応する値にします。ブロック付きでない 場合は常に others の値を使います。
ブロックらしき記法は見当たらないので、merge
メソッドの戻り値は{ "a" => 100, "b" => 300 }
になるかと思います。
>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
=> {"a"=>100, "b"=>300}
予想通りでした。
余談 - ブロック付きのmerge
メソッド
Rubyリファレンスマニュアル(るりま)におけるHash#mergeの解説には、以下の記述があります。
ブロック付きのときはブロックを呼び出してその返す値を重複キーに対応する値にします。
モノは試しなので、実際にやってみましょう。
>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 }) do |key, foo_val, bar_val|
?> foo_val - bar_val
>> end
=> {"a"=>100, "b"=>-100}
200-300は-100です。重複するキー"b"
の値に対し、確かにブロック内容通りの演算がされていますね。
Railsのstylesheet_link_tag
メソッドの呼び出し
Railsチュートリアルには、以下のコードが登場します。
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
Railsチュートリアルによれば、この部分は、Railsのstylesheet_link_tag
メソッドを2つの引数で呼び出しています。重要なのは以下の点です。
- メソッド呼び出しの丸括弧
()
が省略されている-
p({})
のような特別な場合3を除き、Rubyでは、メソッドの引数を明示する丸括弧は必須ではない
-
- ハッシュの波括弧
{}
が省略されている- ハッシュがメソッドの最後の引数である場合、波括弧
{}
は省略できる
- ハッシュがメソッドの最後の引数である場合、波括弧
- 式が2行にわたっている
- Rubyでは、式を完了させない状態で改行4させれば、複数行にわたる式を記述できる
引数が3つあるように見えますが、media: 'all', 'data-turbolinks-track': 'reload'
というのは、実は一つのハッシュであり、これで一つの引数です。
よって、例えば以下の2つの式は等価となります。
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload'
stylesheet_link_tag('application',
{media: 'all',
'data-turbolinks-track': 'reload' })
-
ひとまず「メソッドと同様に実行できるが、レシーバを必要としないもの」と認識しています。
puts
もモジュール関数ですね。 ↩ -
Railsチュートリアルにおける「
p
メソッドというショートカット」という言い回しには、なんとなく違和感がありました。 ↩ -
p({})
の場合、メソッド呼び出しの丸括弧が明示的に記述されていなければ、{}
が引数として解釈されません。 ↩ -
行の最後に演算子(
+
など)やカンマ,
などがある場合、改行があっても式は完了しません。 ↩