2010-08-04 17 views
14

Mam klasę, która wewnętrznie jest po prostu tablicą liczb całkowitych. Raz skonstruowana tablica nigdy się nie zmienia. Chciałbym wstępnie obliczyć dobry kod skrótu, aby ta klasa mogła być bardzo wydajnie używana jako klucz w słowniku. Długość tablicy wynosi mniej niż około 30 elementów, a liczby całkowite wynoszą ogólnie od -1000 do 1000.C# hashcode dla tablicy int

+1

słownik klucz jest unikalny i jeśli obiekt sklep tablica wartości, a klucz jest obliczany na podstawie ich wtedy nie ma gwarancji, że można uzyskać unikatowy klucz hash dla słownika –

+1

@Fadrian: PO nie chcesz obliczyć klucz, ale wartość HashValue. Sprawdź, co to oznacza. Wartości haashvalues ​​są pseudo-unikalne. –

+0

Dzięki Henk. Wiem, jak działa wartość hash, i mogłem źle odczytać zamiar pytania, kiedy opublikowałem komentarz, i to świetnie, że to wskazałeś. –

Odpowiedz

20

Niezbyt mądra, ale wystarczające dla większości celów praktycznych:

EDIT: zmieniony z powodu komentarza Henk Holterman, dzięki za to.

int hc=array.Length; 
for(int i=0;i<array.Length;++i) 
{ 
    hc=unchecked(hc*314159 +array[i]); 
} 
return hc; 

Jeśli potrzebujesz czegoś bardziej wyrafinowane, look here.

+10

Wygląda OK, ale 314159 może być nieco większy. Liczba podobna do 17 lub 31 również byłaby przyjemna. Oraz: 'hc = niezaznaczone (hc * SHIFTVAL + array [i]);' niezależne od ustawień kompilatora. –

+1

Tak, można to z pewnością poprawić na wiele różnych sposobów, awansował swój komentarz. –

+0

punkty finalne rzeczywiście nie są istotne, ale zdecydowanie poleciłbym operatorowi "niezaznaczonemu()". –

0

Myślę, że wybór dobrego algorytmu hashowania musiałby być oparty na rozkładzie (w sensie prawdopodobieństwa) wartości całkowitych.

Wystarczy popatrzeć na Wikipedia dla listy algorytmów

1

Wszelkie CRC (lub nawet XOR) powinny być w porządku.

+2

XOR nigdy nie przesunie się poza okno -/+ 1000 –

+0

@Henk Holterman: Niestety, nie rozumiem. Wciąż masz 10 bitów prawidłowego CRC, jeśli wartości są ograniczone. Edycja: Właściwie pozostałe bity zmieniają się w zależności od znaku. – leppie

+1

CRC jest OK, ale przesada, po prostu XOR-ing wartości (bez zmiany) nie jest OK. –

2

dla tablicy wartości ogólnie między -1000 i 1000, to pewnie użyć czegoś takiego:

static int GetHashCode(int[] values) 
{ 
    int result = 0; 
    int shift = 0; 
    for (int i = 0; i < values.Length; i++) 
    { 
     shift = (shift + 11) % 21; 
     result ^= (values[i]+1024) << shift; 
    } 
    return result; 
} 
+2

FYI, wybrałem numer 11, ponieważ 11 bitów jest to, co jest konieczne do przechowywania zakresu 2048 różnych wartości (-1000 do +1000 jest 2000, który jest blisko). Wybrałem liczbę 21, ponieważ 32-bitowa liczba całkowita minus 11 bitów równa się 21 bitom. Przesunięcie w lewo 21 bitów pozostawi 11 bitów zawierających wartość od 0 do 2048. – BlueMonkMN

3

Można użyć CRC32 kontrolną. Oto kod:

[CLSCompliant(false)] 
public class Crc32 { 
    uint[] table = new uint[256]; 
    uint[] Table { get { return table; } } 

    public Crc32() { 
     MakeCrcTable(); 
    } 
    void MakeCrcTable() { 
     for (uint n = 0; n < 256; n++) { 
      uint value = n; 
      for (int i = 0; i < 8; i++) { 
       if ((value & 1) != 0) 
        value = 0xedb88320^(value >> 1); 
       else 
        value = value >> 1; 
      } 
      Table[n] = value; 
     } 
    } 
    public uint UpdateCrc(uint crc, byte[] buffer, int length) { 
     uint result = crc; 
     for (int n = 0; n < length; n++) { 
      result = Table[(result^buffer[n]) & 0xff]^(result >> 8); 
     } 
     return result; 
    } 
    public uint Calculate(Stream stream) { 
     long pos = stream.Position; 
     const int size = 0x32000; 
     byte[] buf = new byte[size]; 
     int bytes = 0; 
     uint result = 0xffffffff; 
     do { 
      bytes = stream.Read(buf, 0, size); 
      result = UpdateCrc(result, buf, bytes); 
     } 
     while (bytes == size); 
     stream.Position = pos; 
     return ~result; 
    } 
} 
+5

To wydaje się zbyt skomplikowane dla tablicy ~ 30 liczb całkowitych od -1000 do 1000. Wymaga to konwersji tablicy liczb całkowitych na tablicę bajtów lub strumienia najpierw ponieważ nie ma funkcji, która akceptuje tablicę liczb całkowitych jako dane wejściowe, prawda? – BlueMonkMN

+0

Łatwo jest przekonwertować każdą int na bajt []: int value = 0; bajt [] bajtów = BitConverter.GetBytes (wartość); Bajty te mogą służyć do obliczania sumy kontrolnej zamiast bajtów odczytanych ze strumienia. – osprey

+0

Tak, ale zaniedbałeś fakt, że musisz przekonwertować całą tablicę na bajty. To też jest łatwe, ale wciąż kończy się znacznym obciążeniem złożoności kodu i czasem działania w stosunku do rozwiązania specjalnie ukierunkowanego na mieszanie tablicy liczb całkowitych bezpośrednio. – BlueMonkMN

0

Można przyjąć inne podejście i używać słownika rekurencyjnej dla każdej wartości w swojej tablicy int. W ten sposób możesz opuścić .net, aby wykonać mieszanie typu pierwotnego.

internal class DictionaryEntry<TKey, TValue> 
{ 
    public Dictionary<TKey, DictionaryEntry<TKey, TValue>> Children { get; private set; } 
    public TValue Value { get; private set; } 
    public bool HasValue { get; private set; } 

    public void SetValue(TValue value) 
    { 
     Value = value; 
     HasValue = true; 
    } 

    public DictionaryEntry() 
    { 
     Children = new Dictionary<TKey, DictionaryEntry<TKey, TValue>>(); 
    } 
} 

internal class KeyStackDictionary<TKey, TValue> 
{ 
    // Helper dictionary to work with a stack of keys 
    // Usage: 
    // var dict = new KeyStackDictionary<int, string>(); 
    // int[] keyStack = new int[] {23, 43, 54}; 
    // dict.SetValue(keyStack, "foo"); 
    // string value; 
    // if (dict.GetValue(keyStack, out value)) 
    // { 
    // } 

    private DictionaryEntry<TKey, TValue> _dict; 

    public KeyStackDictionary() 
    { 
     _dict = new DictionaryEntry<TKey, TValue>(); 
    } 

    public void SetValue(TKey[] keyStack, TValue value) 
    { 
     DictionaryEntry<TKey, TValue> dict = _dict; 

     for (int i = 0; i < keyStack.Length; i++) 
     { 
      TKey key = keyStack[i]; 
      if (dict.Children.ContainsKey(key)) 
      { 
       dict = dict.Children[key]; 
      } 
      else 
      { 
       var child = new DictionaryEntry<TKey, TValue>(); 
       dict.Children.Add(key, child); 
       dict = child; 
      } 

      if (i == keyStack.Length - 1) 
      { 
       dict.SetValue(value); 
      } 
     } 
    } 

    // returns false if the value is not found using the key stack 
    public bool GetValue(TKey[] keyStack, out TValue value) 
    { 
     DictionaryEntry<TKey, TValue> dict = _dict; 

     for (int i = 0; i < keyStack.Length; i++) 
     { 
      TKey key = keyStack[i]; 

      if (dict.Children.ContainsKey(key)) 
      { 
       dict = dict.Children[key]; 
      } 
      else 
      { 
       break; 
      } 

      if (i == keyStack.Length - 1 && dict.HasValue) 
      { 
       value = dict.Value; 
       return true; 
      } 
     } 

     value = default(TValue); 
     return false; 
    } 
}