まず、この動作は配列だけでなく、後で変更されるデフォルト値(ハッシュや文字列など)にも適用されることに注意してください。
TL; DR:Hash.new { |h, k| h[k] = [] }
最も慣用的なソリューションが必要で、その理由を気にしない場合に使用します。
機能しないもの
なぜHash.new([])
動かないのか
Hash.new([])
動作しない理由をさらに詳しく見てみましょう。
h = Hash.new([])
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["a", "b"]
h[1] #=> ["a", "b"]
h[0].object_id == h[1].object_id #=> true
h #=> {}
デフォルトのオブジェクトが再利用および変更されていることがわかります(これは、唯一のデフォルト値として渡されるためです。ハッシュには、新しい新しいデフォルト値を取得する方法がないためです)。しかし、キーや値がないのはなぜですか。配列では、h[1]
まだ値を提供していますか?ここにヒントがあります:
h[42] #=> ["a", "b"]
各[]
呼び出しによって返される配列は、デフォルト値にすぎません。これは、これまでずっと変更してきたため、新しい値が含まれています。<<
はハッシュに割り当てないため(=
現在の†がないとRubyで割り当てを行うことはできません)、実際のハッシュには何も入れません。代わりに、次のように使用する必要があります<<=
(これは<<
そのまま+=
です+
)。
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
これは次と同じです:
h[2] = (h[2] << 'c')
なぜHash.new { [] }
動かないのか
を使用Hash.new { [] }
すると、元のデフォルト値を再利用および変更する問題が解決されます(指定されたブロックが毎回呼び出され、新しい配列が返されるため)。ただし、割り当ての問題は解決されません。
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
何が機能するか
割り当て方法
私たちは常に使用することを覚えていれば<<=
、その後Hash.new { [] }
で(私は見たことがない実行可能な解決策が、それは少し奇妙と非慣用的だ<<=
野生で使用されます)。また、<<
誤って使用すると、微妙なバグが発生しやすくなります。
変更可能な方法
状態のドキュメントHash.new
(強調は私自身):
ブロックが指定されている場合、ハッシュオブジェクトとキーを使用して呼び出され、デフォルト値を返す必要があります。必要に応じて、ハッシュに値を格納するのはブロックの責任です。
したがって<<
、<<=
次の代わりに使用したい場合は、ブロック内からハッシュにデフォルト値を格納する必要があります。
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
これにより、割り当てが(を使用する<<=
)個々の呼び出しからに渡されるブロックに効果的に移動し、を使用するHash.new
場合の予期しない動作の負担がなくなります<<
。
この方法と他の方法には機能的な違いが1つあることに注意してください。この方法では、読み取り時にデフォルト値が割り当てられます(割り当ては常にブロック内で行われるため)。例えば:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
不変の方法
うまく機能しているのになぜHash.new([])
機能しないのか疑問に思われるかもしれませんHash.new(0)
。重要なのは、Rubyの数値は不変であるため、当然のことながら、それらをインプレースで変更することはありません。デフォルト値を不変として処理した場合、次のように使用することもできますHash.new([])
。
h = Hash.new([].freeze)
h[0] += ['a'] #=> ["a"]
h[1] += ['b'] #=> ["b"]
h[2] #=> []
h #=> {0=>["a"], 1=>["b"]}
ただし、注意してください([].freeze + [].freeze).frozen? == false
。したがって、不変性が全体にわたって維持されるようにする場合は、新しいオブジェクトを再度フリーズするように注意する必要があります。
結論
すべての方法の中で、私は個人的には「不変の方法」を好みます。一般に、不変性は物事についての推論をはるかに簡単にします。結局のところ、これは、予期しない動作が隠されたり微妙に行われたりする可能性がない唯一の方法です。ただし、最も一般的で慣用的な方法は「変更可能な方法」です。
最後に、このハッシュのデフォルト値の動作はRuby Koansに記載されています。
†これは厳密には真実ではありません。instance_variable_set
バイパスのような方法ですが、のl値を=
動的にすることはできないため、メタプログラミングのために存在する必要があります。