Po pierwsze, zwróć uwagę, że to zachowanie dotyczy każdej wartości domyślnej, która jest następnie modyfikowana (np. Skróty i ciągi znaków), a nie tylko tablice.
TL; DR : użyj, Hash.new { |h, k| h[k] = [] }
jeśli chcesz najbardziej idiomatycznego rozwiązania i nie obchodzi cię dlaczego.
Co nie działa
Dlaczego Hash.new([])
nie działa
Przyjrzyjmy się dokładniej, dlaczego Hash.new([])
nie działa:
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 #=> {}
Widzimy, że nasz domyślny obiekt jest ponownie używany i mutowany (to dlatego, że jest przekazywany jako jedyna wartość domyślna, hash nie ma możliwości uzyskania nowej, nowej wartości domyślnej), ale dlaczego nie ma kluczy ani wartości w tablicy, mimo że h[1]
nadal podaje nam wartość? Oto wskazówka:
h[42] #=> ["a", "b"]
Tablica zwracana przez każde []
wywołanie jest po prostu wartością domyślną, którą przez cały ten czas modyfikowaliśmy, więc teraz zawiera nasze nowe wartości. Ponieważ <<
nie przypisuje się do skrótu (w Rubim nigdy nie ma przypisania bez =
prezentu † ), nigdy nie wstawialiśmy niczego do naszego aktualnego skrótu. Zamiast tego musimy użyć <<=
(co jest <<
tak, jak +=
jest +
):
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
To jest to samo, co:
h[2] = (h[2] << 'c')
Dlaczego Hash.new { [] }
nie działa
Użycie Hash.new { [] }
rozwiązuje problem ponownego użycia i mutowania pierwotnej wartości domyślnej (ponieważ dany blok jest wywoływany za każdym razem, zwracając nową tablicę), ale nie problem z przypisaniem:
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
Co działa
Sposób przydziału
Jeśli pamiętamy, aby zawsze używać <<=
, Hash.new { [] }
jest to realne rozwiązanie, ale jest trochę dziwne i nie idiomatyczne (nigdy nie widziałem <<=
używanego na wolności). Jest również podatny na subtelne błędy, jeśli <<
jest nieumyślnie używany.
Zmienny sposób
Dokumentacja dlaHash.new
stanów (kursywa moja własna):
Jeśli określono blok, zostanie on wywołany z obiektem skrótu i kluczem i powinien zwrócić wartość domyślną. W razie potrzeby blok jest odpowiedzialny za przechowywanie wartości w skrócie .
Musimy więc przechowywać domyślną wartość w hashu z wewnątrz bloku, jeśli <<
zamiast tego chcemy użyć <<=
:
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
To skutecznie przenosi przypisanie z naszych indywidualnych wywołań (których użyje <<=
) do bloku przekazanego do Hash.new
, usuwając ciężar nieoczekiwanego zachowania podczas używania <<
.
Zwróć uwagę, że istnieje jedna funkcjonalna różnica między tą metodą a innymi: w ten sposób przypisuje się wartość domyślną podczas odczytu (ponieważ przypisanie zawsze ma miejsce wewnątrz bloku). Na przykład:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
Niezmienny sposób
Możesz się zastanawiać, dlaczego Hash.new([])
nie działa, a Hash.new(0)
działa dobrze. Kluczem jest to, że liczby w Rubim są niezmienne, więc naturalnie nigdy nie zmutujemy ich w miejscu. Gdybyśmy traktowali naszą domyślną wartość jako niezmienną, moglibyśmy również użyć Hash.new([])
:
h = Hash.new([].freeze)
h[0] += ['a'] #=> ["a"]
h[1] += ['b'] #=> ["b"]
h[2] #=> []
h #=> {0=>["a"], 1=>["b"]}
Pamiętaj jednak, że ([].freeze + [].freeze).frozen? == false
. Jeśli więc chcesz mieć pewność, że niezmienność zostanie zachowana przez cały czas, musisz zadbać o ponowne zamrożenie nowego obiektu.
Wniosek
Ze wszystkich sposobów osobiście wolę „niezmienny sposób” - niezmienność ogólnie sprawia, że rozumowanie na różne tematy jest znacznie prostsze. W końcu jest to jedyna metoda, która nie ma możliwości ukrytego lub subtelnego nieoczekiwanego zachowania. Jednak najbardziej powszechnym i idiomatycznym sposobem jest „sposób zmienny”.
Na koniec, takie zachowanie wartości domyślnych Hash zostało odnotowane w Ruby Koans .
† To nie jest do końca prawdą, metody takie jak instance_variable_set
omijają to, ale muszą istnieć dla metaprogramowania, ponieważ wartość l w =
nie może być dynamiczna.
{ [] }
z<<=
ma najmniejszą liczbę niespodzianek, gdyby nie fakt, że przypadkowo zapominając=
może prowadzić do bardzo mylące sesji debugowania.Określasz, że domyślna wartość skrótu jest odniesieniem do tej konkretnej (początkowo pustej) tablicy.
Myślę, że chcesz:
To ustawia domyślną wartość każdego klucza na nową tablicę.
źródło
Array
instancje przy każdym wywołaniu. Mianowicie:h = Hash.new { |hash, key| hash[key] = []; puts hash[key].object_id }; h[1] # => 16348490; h[2] # => 16346570
. Ponadto: jeśli używasz wersji bloku, która ustawia wartość ({|hash,key| hash[key] = []}
), a nie tej, która po prostu generuje wartość ({ [] }
), potrzebujesz tylko<<
, a nie<<=
podczas dodawania elementów.Operator
+=
zastosowany do tych skrótów działa zgodnie z oczekiwaniami.Może to być spowodowane tym, że
foo[bar]+=baz
jest to cukier składniowy, ponieważfoo[bar]=foo[bar]+baz
gdyfoo[bar]
po prawej stronie=
jest oceniany, zwraca domyślny obiekt wartości, a+
operator jej nie zmieni. Lewa ręka to cukier składniowy dla[]=
metody, która nie zmieni wartości domyślnej .Należy pamiętać, że to nie ma zastosowania w przypadku
foo[bar]<<=baz
gdy będzie to odpowiednikfoo[bar]=foo[bar]<<baz
i<<
będzie zmienić domyślną wartość .Nie znalazłem również różnicy między
Hash.new{[]}
iHash.new{|hash, key| hash[key]=[];}
. Przynajmniej na ruby 2.1.2.źródło
Hash.new{[]}
to, że Ruby 2.1.1 jest taki sam jakHash.new([])
dla mnie z brakiem oczekiwanego<<
zachowania (choć oczywiścieHash.new{|hash, key| hash[key]=[];}
działa). Dziwne małe rzeczyKiedy piszesz,
przekazujesz domyślne odniesienie tablicy do wszystkich elementów w hashu. z tego powodu wszystkie elementy w skrócie odnoszą się do tej samej tablicy.
jeśli chcesz, aby każdy element w skrócie odnosił się do oddzielnej tablicy, powinieneś użyć
aby uzyskać więcej informacji o tym, jak to działa w Rubim, przejdź przez to: http://ruby-doc.org/core-2.2.0/Array.html#method-c-new
źródło
Hash.new { [] }
nie nie działa. Zobacz moją odpowiedź po szczegóły. To też jest już rozwiązanie zaproponowane w innej odpowiedzi.