HashでキーごとにArrayやHashのデフォルト値を持ってそれを破壊的に変更して使いたい時、なぜHash.new { |h, k| h[k] = ... }
と書かないとけないのか、知ってたつもりだったけど老人すぎて忘れたのでメモしておく。
Hash.new の引数に渡すと全てのキーから同じオブジェクトが共有されてしまう
hash = Hash.new([]) # この行で生成されたオブジェクトが全てのキーから参照される
hash[:a] << 1 #=> [1]
hash[:b] << 2 #=> [1, 2]
hash[:a] #=> [1, 2]
単にブロックでオブジェクトを作って戻り値にするだけだと、どこにも参照が保存されないので、デフォルト値の破壊的変更によって値を書き変えても結果が失われてしまう
hash = Hash.new { [] }
hash[:a] << 1 #=> [1]
hash[:a] #=> []
# 代入されない限り常に別のオブジェクトが返る
hash[:a].object_id #=> 47040537710560
hash[:a].object_id #=> 47040542091400
# 単なる破壊的変更ではなく、キーに対する代入をすればこれでも使える
hash[:a] += [1] #=> [1]
hash[:a] #=> [1]
hash[:b] #=> []
デフォルト値を作った後そのキーに対して代入をしておけば、キーごとに、最初に参照した時のオブジェクトが使われるようになる
hash = Hash.new { |h, k| h[k] = [] }
hash[:a] << 1 #=> [1]
hash[:b] << 2 #=> [2]
hash[:a] #=> [1]
hash[:b] #=> [2]
普段あんまり深く意識して使わないけど、ArrayやHashをデフォルト値に持つHashに関して、大抵の場合人間が欲しいものは最初に参照した時に作られたオブジェクトをキーごとに参照し続けられるHashなので、Hash.new { |h, k| h[k] = ... }
のように書いておくと良い。