Beachten Sie zunächst, dass dieses Verhalten für alle Standardwerte gilt, die anschließend mutiert werden (z. B. Hashes und Zeichenfolgen), nicht nur für Arrays.
TL; DR : Verwenden Hash.new { |h, k| h[k] = [] }
Sie diese Option, wenn Sie die idiomatischste Lösung suchen und sich nicht darum kümmern, warum.
Was nicht funktioniert
Warum Hash.new([])
funktioniert das nicht?
Schauen wir uns genauer an, warum Hash.new([])
das nicht funktioniert:
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 #=> {}
Wir können sehen, dass unser Standardobjekt wiederverwendet und mutiert wird (dies liegt daran, dass es als einziger Standardwert übergeben wird und der Hash keine Möglichkeit hat, einen neuen Standardwert zu erhalten), aber warum gibt es keine Schlüssel oder Werte im Array, obwohl h[1]
wir immer noch einen Wert haben? Hier ist ein Hinweis:
h[42] #=> ["a", "b"]
Das von jedem []
Aufruf zurückgegebene Array ist nur der Standardwert, den wir die ganze Zeit mutiert haben und der jetzt unsere neuen Werte enthält. Da <<
es keine Zuordnung zum Hash gibt (es kann in Ruby niemals eine Zuordnung ohne =
Geschenk geben † ), haben wir nie etwas in unseren eigentlichen Hash eingefügt. Stattdessen müssen wir verwenden <<=
(was zu ist <<
wie +=
zu +
):
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
Dies ist das gleiche wie:
h[2] = (h[2] << 'c')
Warum Hash.new { [] }
funktioniert das nicht?
Die Verwendung Hash.new { [] }
löst das Problem der Wiederverwendung und Mutation des ursprünglichen Standardwerts (da der angegebene Block jedes Mal aufgerufen wird und ein neues Array zurückgibt), nicht jedoch das Zuweisungsproblem:
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
Was funktioniert?
Der Zuweisungsweg
Wenn wir daran denken, immer zu verwenden <<=
, dann Hash.new { [] }
ist dies eine praktikable Lösung, aber es ist ein bisschen seltsam und nicht idiomatisch (ich habe es noch nie <<=
in freier Wildbahn gesehen). Es ist auch anfällig für subtile Fehler, wenn <<
es versehentlich verwendet wird.
Der veränderliche Weg
Die Dokumentation fürHash.new
Staaten (Schwerpunkt meine eigene):
Wenn ein Block angegeben wird, wird er mit dem Hash-Objekt und dem Schlüssel aufgerufen und sollte den Standardwert zurückgeben. Es liegt in der Verantwortung des Blocks, den Wert bei Bedarf im Hash zu speichern .
Wir müssen also den Standardwert im Hash innerhalb des Blocks speichern, wenn wir <<
anstelle von <<=
:
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
Dadurch wird die Zuweisung effektiv von unseren einzelnen Aufrufen (die verwendet werden würden <<=
) zu dem Block verschoben, an den übergeben wird Hash.new
, wodurch die Last unerwarteten Verhaltens bei der Verwendung beseitigt wird <<
.
Beachten Sie, dass es einen funktionalen Unterschied zwischen dieser Methode und den anderen gibt: Auf diese Weise wird der Standardwert beim Lesen zugewiesen (da die Zuweisung immer innerhalb des Blocks erfolgt). Beispielsweise:
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
Der unveränderliche Weg
Sie fragen sich vielleicht, warum Hash.new([])
es nicht funktioniert, solange es gut Hash.new(0)
funktioniert. Der Schlüssel ist, dass Numerics in Ruby unveränderlich sind, sodass wir sie natürlich nie an Ort und Stelle mutieren. Wenn wir unseren Standardwert als unveränderlich behandeln würden, könnten wir auch gut verwenden Hash.new([])
:
h = Hash.new([].freeze)
h[0] += ['a'] #=> ["a"]
h[1] += ['b'] #=> ["b"]
h[2] #=> []
h #=> {0=>["a"], 1=>["b"]}
Beachten Sie jedoch, dass ([].freeze + [].freeze).frozen? == false
. Wenn Sie also sicherstellen möchten, dass die Unveränderlichkeit durchgehend erhalten bleibt, müssen Sie darauf achten, das neue Objekt erneut einzufrieren.
Fazit
Von allen Möglichkeiten bevorzuge ich persönlich den „unveränderlichen Weg“ - Unveränderlichkeit macht das Denken über Dinge im Allgemeinen viel einfacher. Es ist schließlich die einzige Methode, bei der kein verstecktes oder subtiles unerwartetes Verhalten möglich ist. Der gebräuchlichste und idiomatischste Weg ist jedoch der „veränderbare Weg“.
Abgesehen davon wird dieses Verhalten der Hash-Standardwerte in Ruby Koans vermerkt .
† Dies ist nicht unbedingt der Fall, Methoden wie instance_variable_set
diese umgehen dies, müssen jedoch für die Metaprogrammierung vorhanden sein, da der l-Wert in =
nicht dynamisch sein kann.
{ [] }
mit<<=
hat die wenigstenen Überraschungen, war es nicht für die Tatsache , dass die versehentlich vergessen=
zu einer sehr verwirrend Debug - Sitzung führen könnte.Sie geben an, dass der Standardwert für den Hash ein Verweis auf dieses bestimmte (anfangs leere) Array ist.
Ich denke du willst:
Dadurch wird der Standardwert für jeden Schlüssel auf ein neues Array festgelegt.
quelle
Array
bei jedem Aufruf neue Instanzen. Das heißt :h = Hash.new { |hash, key| hash[key] = []; puts hash[key].object_id }; h[1] # => 16348490; h[2] # => 16346570
. Außerdem: Wenn Sie die Blockversion verwenden, die den Wert ({|hash,key| hash[key] = []}
) festlegt, und nicht die, die einfach den Wert ( ) generiert{ [] }
, benötigen Sie nur<<
, nicht<<=
beim Hinzufügen von Elementen.Der Operator arbeitet,
+=
wenn er auf diese Hashes angewendet wird, wie erwartet.Dies kann daran
foo[bar]+=baz
ist syntaktischer Zucker für ,foo[bar]=foo[bar]+baz
wennfoo[bar]
auf der rechten Hand=
ausgewertet das zurückStandardWert Objekts und die+
Bediener nicht ändern. Die linke Hand ist syntaktischer Zucker für die[]=
Methode, die den Standardwert nicht ändert .Beachten Sie, dass dies nicht gilt ,
foo[bar]<<=baz
wie es gleichwertig sein wirdfoo[bar]=foo[bar]<<baz
und<<
wird den ändern Standardwert .Auch fand ich keinen Unterschied zwischen
Hash.new{[]}
undHash.new{|hash, key| hash[key]=[];}
. Zumindest auf Rubin 2.1.2.quelle
Hash.new{[]}
dasselbe ist wieHash.new([])
für mich mit dem Mangel an erwartetem<<
Verhalten (obwohl es natürlichHash.new{|hash, key| hash[key]=[];}
funktioniert). Seltsame kleine Dinge, die alle Dinge zerbrechen: /Wenn Sie schreiben,
Sie übergeben die Standardreferenz des Arrays an alle Elemente im Hash. Aus diesem Grund verweisen alle Elemente im Hash auf dasselbe Array.
Wenn Sie möchten, dass jedes Element im Hash auf ein separates Array verweist, sollten Sie verwenden
Weitere Informationen zur Funktionsweise von Ruby finden Sie hier: http://ruby-doc.org/core-2.2.0/Array.html#method-c-new
quelle
Hash.new { [] }
funktioniert nicht . Siehe meine Antwort für Details. Es ist auch schon die in einer anderen Antwort vorgeschlagene Lösung.