2013-03-19 8 views
17

Betrachten Sie das einfache Problem, eine veränderbare Karte mit Spur des Auftretens/Zählungen zu halten, dh mit:Zugriff/initialisieren und zu aktualisieren Werte in einer wandelbaren Karte

val counts = collection.mutable.Map[SomeKeyType, Int]() 

Mein aktueller Ansatz einen Zähler zu inkrementieren ist:

counts(key) = counts.getOrElse(key, 0) + 1 
// or equivalently 
counts.update(key, counts.getOrElse(key, 0) + 1) 

Das fühlt sich irgendwie etwas ungeschickt an, weil ich den Schlüssel zweimal angeben muss. In Bezug auf die Leistung würde ich auch erwarten, dass key zweimal in der Karte liegen muss, was ich gerne vermeiden würde. Interessanterweise würde dieses Zugriffs- und Aktualisierungsproblem nicht auftreten, wenn Int einen Mechanismus zur Verfügung stellen würde, um sich selbst zu modifizieren. Der Wechsel von Int zu einer Counter Klasse, die eine increment Funktion würde zum Beispiel erlaubt es:

// not possible with Int 
counts.getOrElseUpdate(key, 0) += 1 
// but with a modifiable counter 
counts.getOrElseUpdate(key, new Counter).increment 

Irgendwie erwarte ich immer die folgende Funktionalität mit einer wandelbaren Karte haben (etwas ähnlich transform aber ohne eine neue Kollektion Rückkehr und auf einem bestimmten Schlüssel mit einem Standardwert):

// fictitious use 
counts.updateOrElse(key, 0, _ + 1) 
// or alternatively 
counts.getOrElseUpdate(key, 0).modify(_ + 1) 

aber soweit ich sehen kann, eine solche Funktionalität nicht vorhanden ist. Wäre es nicht sinnvoll (Leistung und Syntax), eine solche f: A => A In-Place-Modifikationsmöglichkeit zu haben? Wahrscheinlich verpasse ich hier nur etwas ... Ich denke, es muss eine bessere Lösung für dieses Problem geben, die eine solche Funktionalität überflüssig macht.

Update:

ich geklärt haben sollte, dass ich bin mir dessen bewusst withDefaultValue aber das Problem bleibt das gleiche: Durchführung von zwei Lookups noch zweimal so langsam als ein, egal ob es sich um eine O (1) Operation oder nicht. Ehrlich gesagt, würde ich in vielen Situationen mehr als glücklich sein, eine Beschleunigung von Faktor 2 zu erreichen. Und natürlich kann die Konstruktion des Modifikationsverschlusses oft außerhalb der Schleife bewegt werden, so dass dies im Vergleich zu einem Lauf kein großes Problem ist Operation unnötig zweimal.

+2

Key-Lookup in einer Standard-Map sollte normalerweise O (1) sein, also gibt es keine große Strafe, wenn man zweimal nachschaut - wahrscheinlich weniger, als die Construction für den Übergang der Funktion in updateOrElse. – Impredicative

+1

@Impredicative: Das ist ein guter Punkt in diesem Beispiel. Aber die Funktionalität in der Eigenschaft selbst macht davon keine Annahme. Zum Beispiel wird "Map" auch durch "TreeMap" und "ListMap" implementiert, die O (log N) bzw. O (N) sind. Ohne eine O (1) -Annahme zu machen, wäre daher eine Modifizierung in situ im Allgemeinen immer noch wünschenswert. – bluenote10

+1

Ich bin bei dir, bluenote10 - Ich war mir sicher, dass da etwas wie map.update (key, initValue) {} wäre, weil es wesentlich besser ist * und * sauberer ist. Wenn Leistung nicht wichtig wäre, würden wir vermutlich keine veränderbare Map verwenden. Und wie in einem anderen Kommentar erwähnt, ist '(_ + 1)' * keine * Schließung, da es nicht über irgendwelche freien Variablen schließt - es gibt nichts zu konstruieren. – AmigoNico

Antwort

20

Sie können die Karte mit einem Standardwert erstellen, die Sie folgendes erlauben würde zu tun:

scala> val m = collection.mutable.Map[String, Int]().withDefaultValue(0) 
m: scala.collection.mutable.Map[String,Int] = Map() 

scala> m.update("a", m("a") + 1) 

scala> m 
res6: scala.collection.mutable.Map[String,Int] = Map(a -> 1) 

Als imprädikative, Lookups sind schnelle Karte erwähnt, damit ich nicht etwa 2-Lookups kümmern.

Update:

Wie Debilski Sie dies wies darauf hin, kann mehr einfach tun, auch indem Sie folgendermaßen vorgehen:

scala> val m = collection.mutable.Map[String, Int]().withDefaultValue(0) 
scala> m("a") += 1 
scala> m 
res6: scala.collection.mutable.Map[String,Int] = Map(a -> 1) 
+3

Beachten Sie, dass 'm (" a ") + = 1 'ist Zucker (^) für' m ("a") = m ("a") + 1' ist Zucker für 'm.update (" a ", m ("a") + 1) '. (^ oder besser Zucker durch Konvention, als die '+ =' Methode möglicherweise direkt auf 'muable.Map' implementiert) – Debilski

+0

Danke! Ich habe meine Antwort aktualisiert, um das zu reflektieren! – coltfred

+0

Ich kannte den Standardwert Ansatz, aber leider habe ich vergessen, es in meiner Frage zu erwähnen. Ich habe es hier nicht berücksichtigt, da meine eigentliche Sorge (das doppelte Nachschlagen) mit diesem Ansatz nicht gelöst ist. Danke trotzdem! – bluenote10

1

ich zu faul initialisieren meine wandelbar Karte wollte stattdessen eine Falte zu tun (für die Speichereffizienz). Die collection.mutable.Map.getOrElseUpdate() Methode entsprach meinen Zwecken. Meine Karte enthielt ein veränderbares Objekt zum Summieren von Werten (wiederum aus Gründen der Effizienz).

 val accum = accums.getOrElseUpdate(key, new Accum) 
     accum.add(elem.getHours, elem.getCount) 

collection.mutable.Map.withDefaultValue() halten nicht den Standardwert für einen nachfolgenden angeforderten Schlüssel.

+0

Beachten Sie, dass sich die Frage speziell auf den Fall bezieht, in dem "getOrElseUpdate" nicht anwendbar ist (Skalare), das ist sogar das Beispiel, das ich in der Frage angegeben habe. Wenn Sie veränderbare Objekte haben, ist das die offensichtliche Wahl. – bluenote10

Verwandte Themen