Подтвердить что ты не робот

Сделать HashSet <string> без учета регистра

У меня есть метод с параметром HashSet. И мне нужно делать без учета регистра Содержит внутри него:

public void DoSomething(HashSet<string> set, string item)
{
    var x = set.Contains(item);
    ... 
}

Можно ли сделать существующий HashSet без учета регистра (не создавать новый)?

Я ищу решение с наилучшими характеристиками.

Edit

Содержит многократные вызовы. Поэтому расширения IEnumerable не приемлемы для меня из-за более низкой производительности, чем собственный метод HashSet Contains.

Решение

Так как ответ на мой вопрос НЕТ, это невозможно, я создал и использовал следующий метод:

public HashSet<string> EnsureCaseInsensitive(HashSet<string> set)
{
    return set.Comparer == StringComparer.OrdinalIgnoreCase
           ? set
           : new HashSet<string>(set, StringComparer.OrdinalIgnoreCase);
}
4b9b3361

Ответ 1

Конструктор HashSet<T> имеет перегрузку, которая позволяет передавать пользовательский IEqualityComparer<string>. Некоторые из них определены для вас уже в статическом классе StringComparer, некоторые из которых игнорируют регистр. Например:

var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
set.Add("john");
Debug.Assert(set.Contains("JohN"));

Вам нужно будет внести это изменение во время создания HashSet<T>. Как только он существует, вы не можете изменить IEqualityComparer<T> его использование.


Как вы знаете, по умолчанию (если вы не передаете какой-либо IEqualityComparer<T> в конструктор HashSet<T>), вместо этого он использует EqualityComparer<T>.Default.


Изменить

Вопрос, похоже, изменился после того, как я отправил свой ответ. Если вам нужно сделать поиск без учета регистра в существующем чувствительном к регистру HashSet<string>, вам придется выполнять линейный поиск:

set.Any(s => string.Equals(s, item, StringComparison.OrdinalIgnoreCase));

Ничего подобного.

Ответ 2

Вы не можете волшебным образом заставить регистр HashSet (или Словарь) вести себя без учета регистра.

Вы должны воссоздать его внутри своей функции, если не можете полагаться на входящий HashSet, который не учитывает регистр.

Самый компактный код - используйте конструктор constructor из существующего набора:

var insensitive = new HashSet<string>(
   set, StringComparer.InvariantCultureIgnoreCase);

Обратите внимание, что копирование HashSet так же дорого, как обход всех элементов, поэтому, если ваша функция выполняет только поиск, будет дешевле (O (n)) перебирать все элементы. Если ваша функция вызывается несколько раз для поиска без учета регистра, вам следует вместо этого передать ей правильный HashSet.

Ответ 3

HashSet предназначен для быстрого поиска элементов в соответствии с его хэширующей функцией и компаратором равенства. То, о чем вы просите, действительно найти элемент, соответствующий "некоторым другим" условиям. Представьте, что у вас есть объекты Set<Person>, которые используют только Person.Name для сравнения, и вам нужно найти элемент с некоторым заданным значением Person.Age.

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

Ответы до сих пор суть вариации вышеизложенного, я думал добавить это, чтобы прояснить основную проблему.

Ответ 4

Предполагая, что у вас есть этот метод расширения:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

Вы можете просто использовать это:

set = set.Select(n => n.ToLowerInvariant()).ToHashSet();

Или вы могли бы просто сделать это:

set = new HashSet(set, StringComparer.OrdinalIgnoreCase); 
//or InvariantCultureIgnoreCase or CurrentCultureIgnoreCase

Ответ 5

Конструктор HashSet может принять альтернативу IEqualityComparer, которая может переопределить, как определяется равенство. См. Список конструкторов здесь.

Класс StringComparer содержит кучу статических экземпляров IEqualityComparers для строк. В частности, вас, возможно, интересует StringComparer.OrdinalIgnoreCase. Здесь - документация StringComparer.

Обратите внимание, что другой конструктор принимает IEnumerable, поэтому вы можете построить новый HashSet из своего старого, но с IEqualityComparer.

Итак, все вместе, вы хотите преобразовать HashSet следующим образом:

var myNewHashSet = new HashSet(myOldHashSet, StringComparer.OrdinalIgnoreCase);

Ответ 6

Если вы хотите оставить оригинальную версию с учетом регистра, вы можете просто запросить ее с помощью linq с нечувствительностью к регистру:

var contains = set.Any(a => a.Equals(item, StringComparison.InvariantCultureIgnoreCase));

Ответ 7

Теперь вы можете использовать

set.Contains(item, StringComparer.OrdinalIgnoreCase);

без необходимости заново создавать HashSet