Próbuję utworzyć tabelę wyszukiwania słownika w języku C #. Muszę przekształcić 3 krotki wartości w jeden ciąg. Próbowałem używać tablic jako kluczy, ale to nie zadziałało i nie wiem, co jeszcze zrobić. W tym momencie rozważam utworzenie Dictionary of Dictionaries of Dictionaries, ale prawdopodobnie nie byłby to zbyt ładny widok, chociaż tak bym to zrobił w javascript.
c#
dictionary
hashtable
tuples
AlexH
źródło
źródło
GetHashCode
implementacja nie jest zbyt dobra. Jest niezmienna w przypadku permutacji pól.new object()
być równy drugiemunew object()
? To nie tylko proste porównanie referencyjne ... spróbuj:bool test = new Tuple<int, string>(1, "foo").Equals(new Tuple<int, string>(1, "Foo".ToLower()));
Pomiędzy podejściami opartymi na krotkach a zagnieżdżonymi słownikami prawie zawsze lepiej jest wybrać krotkę.
Z punktu widzenia konserwacji ,
znacznie łatwiej jest zaimplementować funkcjonalność, która wygląda następująco:
niż
od strony callee. W drugim przypadku każde dodawanie, wyszukiwanie, usuwanie itp. Wymaga działania na więcej niż jednym słowniku.
Ponadto, jeśli twój klucz złożony będzie wymagał w przyszłości jednego więcej (lub mniej) pola, będziesz musiał znacznie zmienić kod w drugim przypadku (słownik zagnieżdżony), ponieważ musisz dodać kolejne zagnieżdżone słowniki i kolejne sprawdzenia.
Z punktu widzenia wydajności najlepszym wnioskiem, jaki można wyciągnąć, jest samodzielne zmierzenie. Ale istnieje kilka teoretycznych ograniczeń, które możesz rozważyć wcześniej:
W przypadku zagnieżdżonego słownika, posiadanie dodatkowego słownika dla każdego klucza (zewnętrznego i wewnętrznego) będzie wiązało się z pewnym narzutem pamięci (większym niż to, co wiązałoby się z utworzeniem krotki).
W przypadku zagnieżdżonego słownika każda podstawowa czynność, taka jak dodawanie, aktualizacja, wyszukiwanie, usuwanie itp. Musi być wykonywana w dwóch słownikach. Teraz istnieje przypadek, w którym zagnieżdżone podejście słownikowe może być szybsze, tj. Gdy szukane dane są nieobecne, ponieważ słowniki pośrednie mogą ominąć pełne obliczanie i porównywanie kodu skrótu, ale z drugiej strony należy to sprawdzić w czasie, aby mieć pewność. W przypadku obecności danych powinno być wolniejsze, ponieważ wyszukiwania powinny być wykonywane dwukrotnie (lub trzykrotnie w zależności od zagnieżdżenia).
Jeśli chodzi o podejście do krotek, krotki .NET nie są najbardziej wydajne, gdy mają być używane jako klucze w zestawach, ponieważ ich
Equals
iGetHashCode
implementacja powoduje pakowanie typów wartości .Wybrałbym słownik oparty na krotkach, ale jeśli chciałbym uzyskać większą wydajność, użyłbym własnej krotki z lepszą implementacją.
Na marginesie, kilka kosmetyków może sprawić, że słownik będzie fajny:
Wywołania w stylu indeksatora mogą być dużo bardziej przejrzyste i intuicyjne. Np.
Dlatego ujawnij niezbędne indeksatory w swojej klasie słownika, która wewnętrznie obsługuje wstawianie i wyszukiwanie.
Zaimplementuj również odpowiedni
IEnumerable
interfejs i zapewnijAdd(TypeA, TypeB, TypeC, string)
metodę, która zapewni składnię inicjatora kolekcji, na przykład:źródło
string foo = dict[a][b][c]
?a
. Możesz po prostu iterować dowolny słownik, tak jak każdą normalną kolekcję i sprawdzić właściwość klucza, jeśli tak jesta
. Jeśli zawsze chcesz uzyskać pozycję w dictwie według pierwszej właściwości, możesz lepiej zaprojektować słownik jako słownik słowników, jak pokazano w mojej odpowiedzi i zapytaniu, na przykładdict[a]
, co daje inny słownik.4
dla obu kluczya
ib
, możesz uczynić go standardowym słownikiem i dodać wartości takie jakdict[a] = 4
idict[b] = 4
. Może to nie mieć sensu, jeśli logicznie twojaa
ib
powinna być jedna jednostka. W takim przypadku można zdefiniować niestandardowy,IEqualityComparer
który zrównuje dwa wystąpienia kluczy, jeśli którakolwiek z ich właściwości jest równa. Wszystko to można ogólnie zrobić z refelkcją.Jeśli korzystasz z języka C # 7, należy rozważyć użycie krotek wartości jako klucza złożonego. Krotki wartości zwykle oferują lepszą wydajność niż tradycyjne krotki referencyjne (
Tuple<T1, …>
), ponieważ krotki wartości są typami wartości (strukturami), a nie typami odwołań, dzięki czemu unikają alokacji pamięci i kosztów wyrzucania elementów bezużytecznych. Ponadto oferują zwięzłą i bardziej intuicyjną składnię, umożliwiając nazywanie ich pól, jeśli sobie tego życzysz. Implementują równieżIEquatable<T>
interfejs potrzebny dla słownika.źródło
Dobre, czyste, szybkie, łatwe i czytelne sposoby to:
dodaj coś podobnego:
Możesz więc używać go ze słownikiem:
źródło
public TypeA DataA => Item1;
Jeśli z jakiegoś powodu naprawdę chcesz uniknąć tworzenia własnej klasy Tuple lub używania wbudowanej w .NET 4.0, istnieje jeszcze jedno możliwe podejście; możesz połączyć trzy kluczowe wartości razem w jedną wartość.
Na przykład, jeśli te trzy wartości są typami całkowitymi, które razem nie zajmują więcej niż 64 bity, można je połączyć w plik
ulong
.W najgorszym przypadku zawsze możesz użyć łańcucha, o ile upewnisz się, że trzy składowe w nim zawarte są oddzielone jakimś znakiem lub sekwencją, która nie występuje wewnątrz składników klucza, na przykład z trzema liczbami, które możesz wypróbować:
Oczywiście takie podejście wiąże się z pewnym narzutem na kompozycję, ale w zależności od tego, do czego go używasz, może to być na tyle trywialne, że nie przejmuje się tym.
źródło
JavaScriptSerializer
aby połączyć za siebie tablicę typów ciągów i / lub liczb całkowitych. W ten sposób nie musisz samodzielnie wymyślać znaku separatora.key1
,key2
,key3
) były ciągi zawierające deliminator ("#"
)Zastąpiłbym twój Tuple odpowiednim GetHashCode i użył go jako klucza.
Dopóki przeciążasz odpowiednie metody, powinieneś zobaczyć przyzwoitą wydajność.
źródło
Oto krotka .NET w celach informacyjnych:
źródło
Jeśli twój konsumujący kod może poradzić sobie z interfejsem IDictionary <>, zamiast Dictionary, moim odruchem byłoby użycie SortedDictionary <> z niestandardową funkcją porównującą tablice, tj .:
I stwórzmy w ten sposób (używając int [] tylko dla konkretnego przykładu):
źródło