Dostęp bez rozróżniania wielkości liter do słownika ogólnego

244

Mam aplikację korzystającą z zarządzanych bibliotek DLL. Jedna z tych bibliotek dll zwraca ogólny słownik:

Dictionary<string, int> MyDictionary;  

Słownik zawiera klawisze z dużymi i małymi literami.

Z drugiej strony otrzymuję listę potencjalnych kluczy (ciągów), ale nie mogę zagwarantować sprawy. Próbuję uzyskać wartość w słowniku za pomocą klawiszy. Ale oczywiście nie powiedzie się, ponieważ mam niedopasowanie wielkości liter:

bool Success = MyDictionary.TryGetValue( MyIndex, out TheValue );  

Miałem nadzieję, że TryGetValue będzie miał flagę ignorowania przypadków, jak wspomniano w dokumencie MSDN , ale wydaje się, że nie jest to poprawne w przypadku ogólnych słowników.

Czy istnieje sposób, aby uzyskać wartość tego słownika ignorując kluczową wielkość liter? Czy istnieje lepsze obejście niż tworzenie nowej kopii słownika z odpowiednim parametrem StringComparer.OrdinalIgnoreCase ?

TocToc
źródło

Odpowiedzi:

514

Nie ma sposobu, aby określić StringComparerpunkt, w którym próbujesz uzyskać wartość. Jeśli się nad tym zastanowić "foo".GetHashCode()i "FOO".GetHashCode()są zupełnie inni, nie ma rozsądnego sposobu na wprowadzenie rozróżniania wielkości liter na mapie mieszającej uwzględniającej wielkość liter.

Możesz jednak przede wszystkim utworzyć słownik bez rozróżniania wielkości liter, używając:

var comparer = StringComparer.OrdinalIgnoreCase;
var caseInsensitiveDictionary = new Dictionary<string, int>(comparer);

Lub utwórz nowy słownik bez rozróżniania wielkości liter z zawartością istniejącego słownika z rozróżnianiem wielkości liter (jeśli masz pewność, że nie występują kolizje wielkości liter): -

var oldDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var newDictionary = new Dictionary<string, int>(oldDictionary, comparer);

Ten nowy słownik używa następnie GetHashCode()implementacji StringComparer.OrdinalIgnoreCaseso comparer.GetHashCode("foo")i comparer.GetHashcode("FOO")daje tę samą wartość.

Alternatywnie, jeśli w słowniku jest tylko kilka elementów i / lub potrzebujesz tylko raz lub dwa razy wyszukać, możesz traktować oryginalny słownik jako IEnumerable<KeyValuePair<TKey, TValue>>i po prostu iterować:

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var value = myDictionary.FirstOrDefault(x => String.Equals(x.Key, myKey, comparer)).Value;

Lub jeśli wolisz, bez LINQ: -

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
int? value;
foreach (var element in myDictionary)
{
  if (String.Equals(element.Key, myKey, comparer))
  {
    value = element.Value;
    break;
  }
}

Oszczędza to kosztów tworzenia nowej struktury danych, ale w zamian kosztem wyszukiwania jest O (n) zamiast O (1).

Iain Galloway
źródło
Rzeczywiście ma to sens. Dziękuję bardzo za wyjaśnienie.
TocToc,
1
Nie ma powodu, aby trzymać stary słownik i tworzyć nowy, ponieważ wszelkie kolizje przypadków spowodują jego wybuch. Jeśli wiesz, że nie dostaniesz kolizji, możesz od samego początku używać rozróżniania wielkości liter.
Rhys Bevilaqua,
2
Minęło dziesięć lat, od kiedy korzystam z .NET i właśnie to rozgryzłem !! Dlaczego używasz Ordinal zamiast CurrentCulture?
Jordan
Cóż, zależy to od pożądanego zachowania. Jeśli użytkownik udostępnia klucz za pośrednictwem interfejsu użytkownika (lub jeśli chcesz rozważyć np. Ss i ß równe), musisz użyć innej kultury, ale biorąc pod uwagę, że wartość jest używana jako klucz dla haszapy pochodzącej z zależność zewnętrzna, myślę, że „OrdinalCulture” jest rozsądnym założeniem.
Iain Galloway
1
default(KeyValuePair<T, U>)nie jest null- to KeyValuePairgdzie Key=default(T)i Value=default(U). Nie można więc użyć ?.operatora w przykładzie LINQ; musisz złapać, FirstOrDefault()a następnie (w tym konkretnym przypadku) sprawdzić, czy Key == null.
asherber
38

Dla was LINQers, którzy nigdy nie używają zwykłego konstruktora słownika:

myCollection.ToDictionary(x => x.PartNumber, x => x.PartDescription, StringComparer.OrdinalIgnoreCase)
Derpy
źródło
8

Nie jest zbyt elegancki, ale na wypadek, gdybyś nie mógł zmienić tworzenia słownika, a wszystko, czego potrzebujesz, to brudny hack, co powiesz na to:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }
Shoham
źródło
13
lub po prostu to: new Dictionary <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Jordan
6
Zgodnie z „Najlepszymi praktykami korzystania z ciągów w .NET Framework” należy używać ToUpperInvariantzamiast ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
Fred
To było dla mnie dobre, ponieważ musiałem retrospektywnie sprawdzić klucze w nieczuły sposób. Usprawniłem to trochę bardziejvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay
Dlaczego nie tylko dict.Keys.Contains("bla", appropriate comparer)? Ponadto nie otrzymasz null dla FirstOrDefault, ponieważ keyvaluepair w C # jest strukturą.
nawfal