LoginSignup
8
8

More than 5 years have passed since last update.

ネストした Hash をまとめて初期化したい

Last updated at Posted at 2015-07-31

きっかけ

以下のようなコードをもっと手軽に書けないかなと思ったのが発端です。

hash = {}
hash[:yuno] = {}
hash[:yuno][:miyako] = {}

方法

@takeyuichi さんが素晴らしい書き方を教えて下さいました。感謝っ…! 圧倒的感謝っ…!

hash = Hash.new { |h, k| h[k] = h.dup.clear }

hash[:yuno][:miyako] #=> {}
hash #=> { :yuno => { :miyako => {} } }

解説

これは Hash のデフォルト値を巧みに利用したテクニックだと思います。

例えば

hash = Hash.new({})

では、ローカル変数 hash のデフォルト値は {} になりますが、
そのデフォルト値の Hash のデフォルト値は特に指定されていないため nil となってしまいます。

hash = Hash.new({})

hash[:yuno] #=> {}
hash[:sae] #=> {}
hash[:yuno][:miyako] #=> nil

つまり、要件を満たすには
「デフォルト値としてハッシュを返す」ハッシュをデフォルト値とする
必要があるわけです (ややこしいですね…) 。

そこで Hash#dup が役立ちます。要は

hash = Hash.new { |h, k| h[k] = h.dup }

hash[:yuno][:miyako] #=> {}
hash #=> {:yuno=>{:miyako=>{}}}

のように Hash である自分自身 をデフォルト値とすればよいのです。
(Hash#dup はデフォルト値も引き継ぎます。)
なるほど :eyes: からうろこ!!!

しかしこのままでは

hash = Hash.new { |h, k| h[k] = h.dup } 

hash[:yuno][:juniors] = ['乃莉', 'なずな']
hash[:yuno][:miyako] #=> { :juniors => ["乃莉", "なずな"] }
hash #=> { :yuno => { :juniors => ["乃莉", "なずな"], :miyako => { :juniors => ["乃莉", "なずな"] } } }

このようにデフォルト値が汚染されて (上書きされて) しまいます。
なので

hash = Hash.new { |h, k| h[k] = h.dup.clear }

hash[:yuno][:juniors] = ['乃莉', 'なずな']
hash[:yuno][:miyako] #=> {}
hash #=> { :yuno => { :juniors => ["乃莉", "なずな"], :miyako => {} } }

と dup のあとで clear を呼んでハッシュの中身をクリアすることで、
デフォルト値の汚染を防ぐことができます。
(Hash#clear はデフォルト値の設定はクリアしません。)

ちょっとした要件がきっかけで、非常に勉強になりました。 :smile:

おまけ

Hash のデフォルト値には注意すべきポイントもあります。
詳しくは ハッシュの安全な扱い方 (デフォルト値とその落とし穴について) にまとめています。

参考

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8