İlk olarak, bu davranışın sadece diziler için değil, sonradan mutasyona uğramış herhangi bir varsayılan değer (örn. Karmalar ve dizeler) için geçerli olduğuna dikkat edin.
TL; DR : Hash.new { |h, k| h[k] = [] }
En deyimsel çözümü istiyorsanız ve nedenini umursamıyorsanız kullanın.
Ne çalışmıyor
Neden Hash.new([])
çalışmıyor
Neden Hash.new([])
işe yaramadığına daha derinlemesine bakalım :
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 #=> {}
Varsayılan nesnemizin yeniden kullanıldığını ve değiştirildiğini görebiliriz (bunun nedeni tek ve tek varsayılan değer olarak aktarılmasıdır, hash'in yeni, yeni bir varsayılan değer elde etme yolu yoktur), ancak neden anahtarlar veya değerler yoktur dizide, h[1]
bize hala bir değer vermesine rağmen ? İşte bir ipucu:
h[42] #=> ["a", "b"]
Her []
çağrının döndürdüğü dizi sadece varsayılan değerdir, bunca zamandır değiştirdiğimiz ve şimdi yeni değerlerimizi içeriyor. Yana <<
karma atamak değildir (bir olmadan Ruby atama söz konusu olamaz =
günümüze † ), bizim gerçek karma içine bir şey koymak hiç. Bunun yerine kullanılmak zorunda <<=
(olmaktır <<
olarak +=
etmektir +
):
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
Bu şununla aynıdır:
h[2] = (h[2] << 'c')
Neden Hash.new { [] }
çalışmıyor
Kullanılması Hash.new { [] }
, orijinal varsayılan değeri yeniden kullanma ve değiştirme sorununu çözer (verilen blok her seferinde çağrıldığı için, yeni bir dizi döndürür), ancak atama sorununu çözmez:
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
Ne işe yarar
Atama yolu
Biz her zaman kullanmayı unutmayın Eğer <<=
, o zaman Hash.new { [] }
olduğu uygulanabilir bir çözüm, ama biraz garip ve (hiç görmedim olmayan deyimsel bulunuyor <<=
vahşi kullanılır). Ayrıca <<
yanlışlıkla kullanılırsa ince hatalara eğilimlidir .
Değişebilir yol
İçin dokümantasyonHash.new
devletler (vurgu sahibi Gözat):
Bir blok belirtilirse, hash nesnesi ve anahtarla çağrılır ve varsayılan değeri döndürmelidir. Gerekirse değeri hash'de saklamak bloğun sorumluluğundadır .
Bu nedenle, eğer <<
yerine kullanmak istiyorsak, hash'deki varsayılan değeri blok içinden saklamalıyız <<=
:
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
Bu, atamayı etkili bir şekilde bireysel çağrılarımızdan (kullanacak olan <<=
) geçirilen bloğa taşır ve kullanım Hash.new
sırasında beklenmedik davranış yükünü ortadan kaldırır <<
.
Bu yöntemle diğerleri arasında bir işlevsel fark olduğuna dikkat edin: bu yol, okuma üzerine varsayılan değeri atar (atama her zaman blok içinde gerçekleştiği için). Örneğin:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
Değişmez yol
Hash.new([])
Çalışırken neden işe yaramadığını merak ediyor olabilirsiniz Hash.new(0)
. Anahtar, Ruby'deki Numerics'in değişmez olmasıdır, bu nedenle doğal olarak onları asla yerinde mutasyona uğratmayız. Varsayılan değerimizi değişmez olarak ele alırsak, biz de Hash.new([])
gayet iyi kullanabiliriz :
h = Hash.new([].freeze)
h[0] += ['a'] #=> ["a"]
h[1] += ['b'] #=> ["b"]
h[2] #=> []
h #=> {0=>["a"], 1=>["b"]}
Ancak, bunu unutmayın ([].freeze + [].freeze).frozen? == false
. Bu nedenle, değişmezliğin baştan sona korunmasını sağlamak istiyorsanız, yeni nesneyi yeniden dondurmaya özen göstermelisiniz.
Sonuç
Tüm yollar arasında, kişisel olarak "değişmez yolu" tercih ederim - değişmezlik, genellikle şeyler hakkında akıl yürütmeyi çok daha basit hale getirir. Sonuçta, gizli veya belirsiz beklenmedik davranış olasılığı olmayan tek yöntemdir. Bununla birlikte, en yaygın ve deyimsel yol "değişebilir yol" dur.
Son olarak, Hash varsayılan değerlerinin bu davranışı Ruby Koans'ta belirtilmiştir .
† Bu kesinlikle doğru değildir, bunu instance_variable_set
atlama gibi yöntemler , ancak metaprogramlama için mevcut olmalıdır, çünkü içindeki l değeri =
dinamik olamaz.