使HashSet <string>不区分大小写


74

我有HashSet参数的方法。我需要在其中包含不区分大小写的包含内容:

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

有什么办法可以使现有的HashSet不区分大小写(不创建新的)?

我正在寻找性能最佳的解决方案。

编辑

包含可以被多次调用。所以IEnumerable扩展对我来说是不可接受的,因为它的性能低于本机的HashSet Contains方法。

既然,回答我的问题是“否”,那是不可能的,我创建并使用了以下方法:

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

5
您可能必须创建一个新的...
NotALie。

可能的重复:stackoverflow.com/questions/2667635/…(请参阅user414076的答案)
神秘的屏幕名称,

1
您需要HashSet通过提供比较器来预先决定是否考虑情况。但是,值得考虑的是,集合{“ A”,“ a”}仅包含一个不区分大小写的比较器。
支出者2013年

Answers:


133

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));

这是不可能的。


如果您要进行单个查找-比循环遍历哈希集还糟
Dave Bish 2013年

@DaveBish我相信OP在我回答之后将其问题改为“不要创建新的”(发布后不久的编辑实际上并不算作编辑)。-如果OP必须使用现有的 进行此操作HashSet<T>,那么他当然必须进行线性时间搜索。
蒂莫西·希尔兹

1
我不是那个意思 如果他只对哈希集进行一次查找,则创建新哈希集比线性扫描的开销更大。(操作未指定)
Dave Bish

3
这就是为什么我编辑答案以包含LINQ线性扫描的原因。:)
Timothy Shields 2013年

1
这是一个替代方案,但我希望上面有一个更清晰的LINQ解决方案。您可以这样使用Enumerable.Contains<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)set.Contains(item, StringComparison.OrdinalIgnoreCase)。尽管Resharper会生成“可能的非预期线性搜索已设置”警告,但它通常会执行相同的线性搜索。
科里奥

7

您不能神奇地使区分大小写的HashSet(或Dictionary)以不区分大小写的方式运行。

如果不能依赖于传入HashSet不区分大小写,则必须在函数内部重新创建一个。

最紧凑的代码-使用现有集合中的构造函数

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

请注意,复制HashSet与遍历所有项目一样昂贵,因此,如果您的函数仅在搜索时执行,则遍历所有项目的成本会更低(O(n))。如果函数多次调用以进行不区分大小写的单个搜索,则应尝试向其传递适当的值HashSet


4

HashSet设计为快速查找元素按其散列函数和平等比较。您真正要寻找的是找到匹配“其他”条件的元素。想象一下,您有一个Set<Person>Person.Name用于比较的对象,并且需要查找具有某些给定值的元素Person.Age

关键是您需要遍历集合的内容以找到匹配的元素。如果您打算经常这样做,则可以使用不区分大小写的比较器来创建其他Set,但必须确保此影子集与原始同步。

到目前为止,答案基本上是上述内容的变体,我想补充一下以澄清基本问题。


3

假设您具有以下扩展方法:

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

1
如果您要进行单个查找-比循环遍历哈希集还糟
Dave Bish

它会占用大量内存并进行大量哈希计算,然后在一次查找后将所有工作丢弃。循环遍历整个哈希集并进行不区分大小写的比较可在常量内存中运行,而不必计算哈希。set无论如何,两者都需要触及全部。

因为制作新的哈希集至少要遍历整个过程!
Dave Bish 2013年

@DaveBish最受好评的答案也正是这样做的……它也需要对其进行重构……
NotALie。

我也张贴在
那张照片上

2

的构造方法HashSet可以采用替代方法IEqualityComparer,该方法可以覆盖确定相等性的方式。请参阅此处的构造函数列表。

该类StringComparer包含一堆IEqualityComparersfor字符串的静态实例。特别是您可能对感兴趣StringComparer.OrdinalIgnoreCase是的文档StringComparer

请注意,另一个构造函数采用IEnumerable,因此您可以使用来构造一个HashSet旧的构造函数IEqualityComparer

因此,总的来说,您希望将其转换HashSet如下:

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

0

如果要保留原始的区分大小写的版本,则可以使用不区分大小写的linq查询它:

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

-1

您现在可以使用

set.Contains(item, StringComparer.OrdinalIgnoreCase);

无需重新创建您的HashSet

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.