2013-03-19 3 views
17

Rozważmy prosty problem stosując zmienny mapę do śledzenia zdarzeń/liczby, tj:dostępu/zainicjować i aktualizacji wartości w zmienny mapie

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

Mojego obecnego podejścia do zwiększany licznik jest:

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

To wydaje mi się trochę niezgrabne, ponieważ muszę dwa razy określić klucz. Jeśli chodzi o wydajność, spodziewam się również, że key musi znajdować się dwa razy na mapie, czego chciałbym uniknąć. Co ciekawe, ten problem z dostępem i aktualizacją nie wystąpiłby, gdyby Int zapewniał pewien mechanizm do modyfikacji. Zmiana z Int do klasy Counter który zapewnia funkcję increment byłoby na przykład umożliwiają:

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

Jakoś mam zawsze spodziewa się mieć następującą funkcjonalność z zmienny mapy (nieco podobny do transform ale bez powrotu nową kolekcję i na konkretnym kluczu o wartości domyślnej):

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

Jednak o ile widzę, taka funkcjonalność nie istnieje. Czy nie miałoby sensu ogólnie (wydajność i składnia) mieć taką możliwość modyfikacji na miejscu? Prawdopodobnie po prostu czegoś tutaj brakuje ... Chyba musi być jakieś lepsze rozwiązanie tego problemu, aby taka funkcjonalność była niepotrzebna?

Aktualizacja:

powinienem wyjaśnić, że jestem świadomy withDefaultValue ale problem pozostaje ten sam: wykonanie dwóch wyszukiwań jest nadal dwukrotnie jako powolny niż jeden, bez względu na to czy jest to O (1) działanie lub nie. Szczerze mówiąc, w wielu sytuacjach byłbym bardziej niż szczęśliwy z powodu przyspieszenia czynnika 2. I oczywiście konstrukcja zamknięcia modyfikacji może często zostać przeniesiona poza pętlę, więc nie jest to duży problem w porównaniu do uruchamiania operacja niepotrzebnie dwa razy.

+2

Klucz odnośników w standardowym mapa powinna zwykle być O (1), więc nie jest to duży kara w poszukiwaniu go dwukrotnie - prawdopodobnie mniej niż jest wypłacana przez konstruowanie zamknięcie dla funkcji, aby przejść do 'updateOrElse'. – Impredicative

+1

@Impredicative: To dobry punkt w tym przykładzie. Ale funkcjonalność samej cechy nie ma żadnego założenia. Na przykład 'Map' jest również implementowany przez' TreeMap' i 'ListMap', które są odpowiednio O (log N) i O (N). Zatem bez założenia O (1), modyfikacja na miejscu byłaby ogólnie pożądana. – bluenote10

+1

jestem z wami, bluenote10 - Byłem pewien, że byłoby coś takiego 'map.update (klucz, initValue) {}' ponieważ wykonuje znacznie lepiej * i * jest czystsze. Jeśli wydajność nie ma znaczenia, prawdopodobnie nie używalibyśmy najpierw mapy Mutable Map. I jak wspomniano w innym komentarzu, '(_ + 1)' jest * nie * zamknięciem, ponieważ nie zamyka żadnych wolnych zmiennych - nie ma nic do skonstruowania. – AmigoNico

Odpowiedz

20

Można by stworzyć mapę z domyślnej wartości, które pozwalają na wykonanie następujących czynności:

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) 

Jak wspomniano Impredicative, mapa wyszukiwań szybko, żeby nie martwić się o 2 wyszukiwań.

Aktualizacja:

Jak Debilski wskazał, można to zrobić jeszcze prościej, wykonując następujące czynności:

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

Uwaga że 'M ("a") + = 1' jest cukier (^) do 'M ("A") = M ("a") + 1 'jest cukier do' m.update ("a", m ("a") + 1) '. (^ lub raczej cukier zgodnie z konwencją, jako metoda '+ =' prawdopodobnie zaimplementowana bezpośrednio na 'mutable.Map') – Debilski

+0

Dzięki! Zaktualizowałem swoją odpowiedź, aby to odzwierciedlić! – coltfred

+0

Byłem świadomy domyślnej metody wartości, ale niestety zapomniałem wspomnieć o tym w moim pytaniu. Nie rozważałem tego tutaj, ponieważ moja prawdziwa troska (podwójne szukanie) nie jest rozwiązana z tym podejściem. Dzięki i tak! – bluenote10

1

chciałem lazy-zainicjować mój zmienny mapę zamiast robić fałdę (dla wydajności pamięci). Metoda collection.mutable.Map.getOrElseUpdate() spełniła moje zadanie. Moja mapa zawierała zmienny obiekt do sumowania wartości (ponownie, dla efektywności).

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

collection.mutable.Map.withDefaultValue() nie przechowuje wartość domyślną dla późniejszego żądanego klucza.

+0

Należy zauważyć, że pytanie było szczególnie zwracając sprawę gdzie 'getOrElseUpdate' nie ma zastosowania (skalary), to nawet na przykładzie dałem w pytaniu. Jeśli masz zmienne obiekty, jest to oczywisty wybór. – bluenote10