Ruby

Rubyのhash宣言方法いろいろ


この記事のサマリ

・Ruby1.9以降は、シンボルをキーとしてhashの宣言として、{key1: value1, key2: value2}といった記法が追加された(よくみかける)

・hashには、newするときにデフォルト値を設定できるが、デフォルト値は共有なので破壊的メソッドで破壊されると思わぬ動作になることがあるので注意しましょう。


背景

Rubyのhashについて、Ruby1.9以降は、シンボルを使ったhash用に新しいリテラルが用意されていて、railsでは当たり前のように使っているのですが、当たり前すぎるからか、ほとんど説明している記事がなかったので初歩的なhashs宣言方法という切り口でまとめました。また、デフォルト値を使っていて「あれ?」ってなったことがあったので備忘としてまとめておきます。


hashとは

任意のオブジェクトのインデックス(キーと呼びます)を使って、キーと値の対応を表現するものです。実体は、Hashクラスのオブジェクトになっています。

他の言語では連想配列なんて呼ばれたりすることもあります。

キーには、文字列やシンボルをよく使います。(文字列やシンボルでなくともキーとすることは可能です。)


シンボルとは

Symbolクラスのオブジェクトです。Symbolは任意の文字列と一対一に対応するオブジェクトであり、同じリテラルから得られるSymbolは、必ず同一オブジェクトになるという特徴があります。文字列とことなり、同じリテラルを利用する場合は、新しく文字列を生成しないため効率がよく、比較も高速といった特徴があり、hashのキーとして使われることも多いです。


hashの宣言方法


空のhashを宣言する

hash = {} #=> {}

hash.class #=> Hash


従来からあるhashの宣言方法

{}の中に、「key => value」という要素を、カンマ区切りで記述することで、内容を設定しつつhashを宣言することが可能です。

# 文字列キーとしたhashの宣言

hash1 = { "key1" => "value1", "key2" => "value2" } # => {"key1"=>"value1", "key2"=>"value2"}
hash1.class #=> Hash

シンボルを利用する場合も同様で、{}の中に「:key -> value」という要素を、カンマ区切りで記述することで、内容を設定しつつhashを宣言することが可能です。(単純にkey部分にシンボルを利用するというだけですので、記法は同じです。)

# シンボルをキーとしたhashの宣言

hash2 = { :key1 => "value1", :key2 => "value2" } # => {:key1=>"value1", :key2=>"value2"}
hash2.class #=> Hash


シンボルをキーとしたhashの宣言(Ruby 1.9で用意追加されたリテラル)

Ruby1.9で、シンボルをキーとしたhashの宣言方法に、新しいリテラルが追加されました。

jsonのような感じで、宣言することができます。

hash3 = { key1: "value1", key2: "value2"} # => {:key1=>"value1", :key2=>"value"

hash3.class #=> Hash

→このような書き方をしても裏側では、シンボルをkeyとしたhashが生成されており、hash2とhash3は、どちらも「:key1」に対して「"value1"」、「:key2」に対して「"value2"」が紐付いたhashが作られます。


Hashのデフォルト値の振る舞い

通常、hashのキーに対応する内容がなかった場合は、nilが返却されるのですが、Hashをnewする際に、デフォルト値を設定することで、この挙動を変更することが可能です。

# ただのhashでは、存在しないkeyを参照すると、nilになる

normal_hash = {} # => {}
normal_hash[:key] # => nil

# hashをnewする際に、デフォルト値を設定した場合
with_default_value_hash = Hash.new("default!!!") # => {}
# 以下のように、デフォルト値が返戻される
with_default_value_hash[:key] # => "default!!!"

注意事項

デフォルト値は共有されるため、予期せぬ事象を招く可能性があります。

例えば、ある存在しないキーに紐づく値に対して、デフォルト値を破壊的メソッドで破壊してしまうと、デフォルト値が変更されることになます。その状態で、別の存在しないキーで参照した場合、変更後の値が取得されるということになります。

例えば

with_default_value_hash = Hash.new("default!!!") # => {}

# :keyで得られるデフォルト値に対して、upcase!で大文字化した場合。。。
with_default_value_hash[:key].upcase! # => "DEFAULT!!!"
# :key2で得られるのがデフォルト値だった場合、upcase!で破壊された値となる
with_default_value_hash[:key2] # => "DEFAULT!!!"

これは、思わぬ副作用が発生してしまう可能性があるため、ブロックを用いて、一つ一つにデフォルト値を設定することで防ぐことが可能です。

with_protected_default_value_hash = Hash.new { "default!!!" } # => {}

with_protected_default_value_hash[:key].upcase! # => "DEFAULT!!!"
with_protected_default_value_hash[:key2] # => "default!!!"