Techioz Blog

Ruby: 存在しないキーの場合、デフォルト値ハッシュを持つハッシュにハッシュを保存する [重複]

概要

Hash.new({}) のネストされた値を保存しようとすると、なぜこのように機能するのでしょうか?ボンネットの下で実際に何が起こっているのでしょうか?そのユースケースは何でしょうか?

デフォルト値が {} のハッシュを作成する場合

a = Hash.new({})
=> {}
a.default
=> {}

存在しないキーをクエリすると、期待どおりに空のハッシュが返されます

a[:foo]
=> {}

ただし、まだ存在しないキーに値を割り当てようとすると、

a[:foo][:bar] = 'baz'
=> "baz"

ハッシュはまだ空のようです

a
=> {}

ただし、親キーを取得すると、ネストされたハッシュが返されます。

a[:foo]
=> {:bar=>"baz"}

さらに混乱するのは、この新しいハッシュが親ハッシュのデフォルト値になっていることです。

a.default
=> {:bar=>"baz"}

存在しないキーをクエリするとその値が返されるようにする

a[:biz]
=> {:bar=>"baz"}

これは次のようにすることで解決できます

a[:foo] = {} unless a.key? :foo
a[:foo][:bar] = 'baz'
a
=> {:foo=>{:bar=>"baz"}}

他の同様の質問でも、 a = Hash.new { |h,k| が提案されています。 h[k] = Hash.new(&h.default_proc) } これは新しいキーを保存するために機能しますが、フェッチ操作用に空のハッシュも作成します。

a[:baz] == 2
a
=> {:baz=>{}}

メソッドを記述する以外に、値を取得するときではなく、値を格納するときに必要に応じてネストされたハッシュを作成するハッシュを取得する方法はありますか?

解決策

Ruby1 では、複製されたデフォルトではなく、デフォルトのオブジェクト参照を保存していることに注意してください。それがあなたの意図ではないとしても、不足しているキーのデフォルトが同じオブジェクトであることを要求しています。 Hash.new(0) のような単純な値の場合、新しい値が割り当てられるときにデフォルトは変更されず、置き換えられますが、ネストされたハッシュでは値を明示的に変更することになります。

あなたが望むのは、これを次のように最小限に表現することです。

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

なぜこのように物事が混ざってしまうのか混乱した場合は、特定のオブジェクトの「アイデンティティ」を伝える object_id を確認してください。

考慮する:

a = Hash.new({ })
a[0].object_id == a[1].object_id
# => true

b = Hash.new { |h,k| h[k] = { } }
b[0].object_id == b[1].object_id
# => false

ここで、各「スロット」が独立していることがわかります。

これらの自動インスタンス化モデルの欠点の 1 つは、ご指摘のように、必ずしも必要ではないエントリが作成されてしまうことです。これを避けるには、次のようにより慎重に作業する必要があります。

if a.key?(:baz) && a[:baz] == 2
  # ...
end

1 JavaScript、Python などでも、少なくとも非プリミティブ型ではこの動作が見られます。