Доступ без учета регистра для универсального словаря


244

У меня есть приложение, которое использует управляемые DLL. Один из этих dll возвращает общий словарь:

Dictionary<string, int> MyDictionary;  

Словарь содержит ключи с прописными и строчными буквами.

С другой стороны, я получаю список потенциальных ключей (строка), однако я не могу гарантировать случай. Я пытаюсь получить значение в словаре, используя ключи. Но, конечно, следующее не получится, так как у меня несоответствие регистра:

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

Я надеялся, что у TryGetValue будет флаг игнорирования, как упомянуто в документе MSDN , но, похоже, это недопустимо для универсальных словарей.

Есть ли способ получить значение этого словаря, игнорируя регистр ключей? Есть ли лучший обходной путь, чем создание новой копии словаря с соответствующим параметром StringComparer.OrdinalIgnoreCase ?


Ответы:


515

Там нет никакого способа указать StringComparerв точке, где вы пытаетесь получить значение. Если вы думаете об этом, "foo".GetHashCode()и "FOO".GetHashCode()все совершенно по-другому, то нет никакого разумного способа реализовать регистронезависимое попадание на чувствительную к регистру хэш-карту.

Однако вы можете создать словарь без учета регистра в первую очередь, используя: -

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

Или создайте новый нечувствительный к регистру словарь с содержимым существующего чувствительного к регистру словаря (если вы уверены, что нет коллизий):

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

Этот новый словарь затем использует GetHashCode()реализацию StringComparer.OrdinalIgnoreCaseтак comparer.GetHashCode("foo")и comparer.GetHashcode("FOO")дает вам то же значение.

В качестве альтернативы, если в словаре всего несколько элементов и / или вам нужно искать только один или два раза, вы можете рассматривать исходный словарь как IEnumerable<KeyValuePair<TKey, TValue>>и просто перебирать его: -

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

Или, если вы предпочитаете, без 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;
  }
}

Это экономит затраты на создание новой структуры данных, но взамен стоимость поиска составляет O (n) вместо O (1).


На самом деле это имеет смысл. Большое спасибо за объяснение.
— TocToc

1
Нет смысла хранить старый словарь и создавать новый экземпляр, так как любые случайные столкновения приведут к его взрыву. Если вы знаете, что не будете сталкиваться с конфликтами, то с самого начала вы можете использовать регистр без учета регистра.
— Рис Бевилаква

2
Прошло уже десять лет, как я использую .NET, и я только что понял это !! Почему вы используете Ordinal вместо CurrentCulture?
— Джордан,

Ну, это зависит от поведения, которое вы хотите. Если пользователь предоставляет ключ через пользовательский интерфейс (или если вам необходимо учитывать, например, ss и ß равные), вам нужно будет использовать другую культуру, но, учитывая, что значение используется в качестве ключа для хэш-карты, полученной из внешняя зависимость, я думаю, «OrdinalCulture» - разумное предположение.
— Иэн Галлоуэй,

1
default(KeyValuePair<T, U>)не null- это KeyValuePairгде Key=default(T)и Value=default(U). Таким образом, вы не можете использовать ?.оператор в примере LINQ; вам нужно взять FirstOrDefault()и затем (для этого конкретного случая) проверить, еслиKey == null .
— Ашербер

38

Для вас, LINQers, которые никогда не используют обычный словарь конструктора:

myCollection.ToDictionary(x => x.PartNumber, x => x.PartDescription, StringComparer.OrdinalIgnoreCase)

8

Это не очень элегантно, но в случае, если вы не можете изменить создание словаря, и все, что вам нужно, это грязный хак, как насчет этого:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }

13
или просто так: новый словарь <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
— Иордания

6
Согласно «Рекомендации по использованию строк в .NET Framework» используйте ToUpperInvariantвместо ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
— Фред

Это было хорошо для меня, когда мне приходилось ретроспективно проверять ключи нечувствительным образом. Я упростила это немного большеvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
— Джей

Почему не просто dict.Keys.Contains("bla", appropriate comparer)? Кроме того, вы не получите null для FirstOrDefault, поскольку пара ключей в C # является структурой.
— Навфал
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.