2016-03-23 25 views
5

W Dictionary<struct,int>: czy jest możliwe Dodać/Ustawić w jednym wywołaniu?C# Słownik Dodaj/Ustaw w jednym wywołaniu ze względu na wydajność

Czy można wykonać poniższy kod tylko w jednym wyszukiwaniu w odniesieniu do wpisu?

_KeyToPoints = new Dictionary<Key,int>(); 

foreach (var entry in billionEntries) 
{ 
    int originalValue; 

    // first lookup 
    _KeyToPoints.TryGetValue(entry.Key, out originalValue); 

    // second lookup 
    _KeyToPoints[key] = originalValue + points;  
} 

Odbywa się to w bardzo ciasnej pętli nad ogromną ilością danych, więc wszystkie działania mają znaczenie.

A może jest lepsza odpowiednia struktura danych?

+1

Nie sądzę, że możesz uniknąć konieczności indeksowania w słowniku dwukrotnie (raz, aby uzyskać istniejącą wartość, i ponownie aby ustawić wartość), ale możesz pominąć ustawienie wartości originalValue na 0, ponieważ TryGetValue zrobi to za ciebie, jeśli klucz nie zostanie znaleziony. I musisz mieć check in przypadku TryGetValue zwraca false. – SlimsGhost

+1

Czy występuje problem z wydajnością _actual_ w stosunku do całego procesu lub czy tylko chcesz go mieć? Wyszukiwanie słownika to O (1), więc nie powinno być zbyt mało wysiłku związanego z wydajnością przy wyszukiwaniu (i zestawie o to chodzi) –

+0

@ SlimsGhost Nie musisz sprawdzać wyniku w tym przypadku - jeśli klucz nie istniał '_KeyToPoints [key]' przechowuje '0 + punktów' w tej pozycji klucza. Kod byłby taki sam, niezależnie od tego, czy klucz istnieje, czy nie. –

Odpowiedz

2

Tak, jest sposób, aby to zrobić, ale ma on wadę. Rozważmy taką klasę:

class Ref<T> 
{ 
    public T Value; 
} 

Można użyć Dictionary<K, Ref<int>> dict a następnie to zrobić:

Ref<int> count; 
if (!dict.TryGetValue(key, out count)) 
{ 
    count = new Ref<int> { Value = 0 }; 
    dict[key] = count; 
} 
count.Value += points; 

Minusem jest to, że teraz masz dodatkowy przedmiot sterty za wpis w swoim słowniku. W zależności od Twojej sytuacji może to być możliwe, ale nie musi.

+0

Czy rzeczywiście jest to szybsze? Prawie wszystkie te same operacje są wykonywane, a pierwsze trafienie (podczas dodawania pozycji do słownika) jest jeszcze cięższe z powodu tworzenia obiektu. –

+1

@QualityCatalyst Oba podejścia musiałyby być profilowane. Pytanie, które zinterpretowałem, było po prostu tym, czy można to zrobić z tylko jednym słownikiem. Właśnie wyjaśniłem, że tak, jest to możliwe i jakościowo scharakteryzowałem jego wadę. Być może profilowanie pokazuje, że jest to okropne podejście do wydajności, ale nie sądzę, że to unieważnia odpowiedź. –

+0

Dzięki, Timothy, ale to stwarza w tym przypadku znacznie większe problemy :) chyba nie określiłem wystarczająco dobrze mojego pytania. Jeśli to naprawdę jedyny sposób, w jaki ja to zaakceptuję –

-1

Bardzo nieprzyjemne rozwiązanie, ale będzie szybsze pod warunkiem, że nazwiesz metodę AddPoints() głównie dla już istniejących pozycji w słowniku.

void AddPoints(T key, int points) 
{ 
    try 
    { 
     _KeyToPoints[key] = _KeyToPoints[key] + points; 
    } 
    catch (ArgumentException) 
    { 
     _KeyToPoints[key] = points; 
    } 
} 

Wyjątki związane z wyrzucaniem i obsługą są bardzo drogie (czasochłonne) i negatywnie wpływają na wydajność. Aby tego uniknąć, możesz wstępnie wypełnić słownik:

void PreFillKeys(params T[] keys) // use an IEnumerable<T> if handier 
{ 
    foreach (T key in keys) 
    { 
     // EITHER THIS: 
     if (!_KeyToPoints.ContainsKey(key)) 
      _KeyToPoints[key] = 0; 
     /* OR THIS (faster if you know none of keys was added before) 
     try 
     { 
      _KeyToPoints[key] = 0; 
     } 
     catch (ArgumentException) 
     { 
      // ignore >> you shouldn't have called 
     } 
     */ 
    } 
} 
0

Można używać starego Hashtable pojemnika:

Hashtable map = new Hashtable(); 
map[key] = value; 

Jeśli klucz nie istnieje, zostanie utworzony automatycznie i dodać go do mapy. Ale będziesz cierpiał z powodu boksowania/rozpakowywania, jeśli użyjesz klucza, który jest wartością typu ...