不区分大小写的列表搜索

219

我有一个包含一堆字符串的列表testList。如果这个字符串在列表中不存在,我想把一个新字符串加入到testList中。因此,我需要对列表进行不区分大小写的搜索并且确保效率。由于Contains没有考虑大小写,所以我不能使用它。为了避免性能问题,我也不想使用ToUpper/ToLower。我找到了以下方法,它是有效的:

    if(testList.FindAll(x => x.IndexOf(keyword, 
                       StringComparison.OrdinalIgnoreCase) >= 0).Count > 0)
       Console.WriteLine("Found in list");

这样可以工作,但它也匹配部分单词。如果列表包含“goat”,我无法添加“oat”,因为它声称“oat”已经在列表中。是否有一种以不区分大小写的方式高效地搜索列表,并且单词必须完全匹配的方法?谢谢

8个回答

541

我知道这是一个旧帖子,但是以防其他人在寻找,你可以使用提供不区分大小写的字符串相等比较器来使用Contains方法,代码如下:

using System.Linq;

// ...

if (testList.Contains(keyword, StringComparer.OrdinalIgnoreCase))
{
    Console.WriteLine("Keyword Exists");
}

根据msdn的说法,这项功能自.net 2.0版本以来就已经可用。


31
这绝对是这里最好的答案。 :) - Joe
29
"Enumerable<T>.Contains"(你所引用的内容)自.NET 2.0以来就存在了。没有List<T>.Contains具有你正在使用的那个重载。" - Adam Sills
52
起初我也没有注意到这个过载问题,但你需要添加 using System.Linq 才会出现。 - Michael
6
StringComparer 类自2.0版本就存在,但是 Contains 方法的那个重载是在3.5版本中引入的。 https://msdn.microsoft.com/zh-cn/library/bb339118(v=vs.110).aspx - Denise Skidmore
1
我本来想给这个答案点个踩,因为我把StringComparer和StringComparison搞混了(而我在string.Equals(...)方法中经常使用StringComparison)。大家不要犯和我一样的错误 :) - Gioce90
显示剩余3条评论

214

不要使用String.IndexOf,而是使用String.Equals来确保你没有部分匹配。同时不要使用FindAll, 因为它会遍历所有元素, 使用FindIndex(它在第一个找到的元素处停止)。

if(testList.FindIndex(x => x.Equals(keyword,  
    StringComparison.OrdinalIgnoreCase) ) != -1) 
    Console.WriteLine("Found in list"); 

你可以使用一些LINQ方法(它们也会在找到第一个匹配项后停止搜索)。

if( testList.Any( s => s.Equals(keyword, StringComparison.OrdinalIgnoreCase) ) )
    Console.WriteLine("found in list");

只是补充一下,在几次快速测试中,第一种方法似乎快了约50%。也许其他人可以确认或否认这一点。 - Brap
11
从.NET 2.0开始,这现在很容易实现 - 可以看下面 shaxby 的回答。 - Joe
3
Contains 方法 shaxby 引用的重载版本(接受一个 IEqualityComparer 参数)是 LINQ 的一部分,所以它肯定不会在 .NET 2.0 之前就已经存在。只有 StringComparer 类已经存在了一段时间。List<T>、ArrayList 和 StringCollection 都没有这个方法(他可能引用的“列表”)。 - Adam Sills
嗯,既然我确实“需要”索引,这绝对是最好的答案。 - Nyerguds
2
第一种解决方案应该使用 List<>.Exists(Predicate<>) 实例方法。同时请注意,如果列表包含 null 条目,则可能会出现问题。在这种情况下,更安全的做法是说 keyword.Equals(x, StringComparison.OrdinalIgnoreCase) 而不是 x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(如果您可以保证 keyword 永远不为 null)。 - Jeppe Stig Nielsen
顺便提一下...对于数组,您可以使用静态的Array.FindIndex(array, predicate) - Nyerguds

24

例如,您可以使用LINQ中的Contains重载和StringComparer静态变体,如下所示:

using System.Linq;

var list = new List<string>();
list.Add("cat");
list.Add("dog");
list.Add("moth");

if (list.Contains("MOTH", StringComparer.OrdinalIgnoreCase))
{
    Console.WriteLine("found");
}

6
只要添加 "using System.Linq",否则您就看不到 .Contains 的那个重载。 - Julian Melville

19

基于Adam Sills上面的答案,这里是一个干净漂亮的Contains扩展方法 :)

///----------------------------------------------------------------------
/// <summary>
/// Determines whether the specified list contains the matching string value
/// </summary>
/// <param name="list">The list.</param>
/// <param name="value">The value to match.</param>
/// <param name="ignoreCase">if set to <c>true</c> the case is ignored.</param>
/// <returns>
///   <c>true</c> if the specified list contais the matching string; otherwise, <c>false</c>.
/// </returns>
///----------------------------------------------------------------------
public static bool Contains(this List<string> list, string value, bool ignoreCase = false)
{
    return ignoreCase ?
        list.Any(s => s.Equals(value, StringComparison.OrdinalIgnoreCase)) :
        list.Contains(value);
}

3
根据Lance Larsen的回答,这是一个使用推荐的string.Compare而不是string.Equals的扩展方法。
建议您使用带有StringComparison参数的String.Compare重载。不仅可以通过使用这些重载来定义您所期望的确切比较行为,还可以使您的代码对其他开发人员更易读。[Josh Free @ BCL Team Blog]
public static bool Contains(this IEnumerable<string> source, string toCheck, StringComparison comp)
{
    return
       source != null &&
       !string.IsNullOrEmpty(toCheck) &&
       source.Any(x => string.Compare(x, toCheck, comp) == 0);
}

我稍微修改了这个,使得第一个参数是 this IEnumerable<string> source,以增加兼容性并允许更好地与 .Where() 等方法链式调用。 - The Thirsty Ape
谢谢,采纳了你对帖子的改进。 - dontbyteme

0

你正在检查IndexOf的结果是否大于或等于0,这意味着匹配是否从字符串的任何位置开始。尝试检查它是否等于0:

if (testList.FindAll(x => x.IndexOf(keyword, 
                   StringComparison.OrdinalIgnoreCase) >= 0).Count > 0)
   Console.WriteLine("Found in list");

现在,“goat”和“oat”不匹配,但“goat”和“goa”会匹配。为了避免这种情况,您可以比较两个字符串的长度。

为了避免所有这些复杂性,您可以使用字典而不是列表。键将是小写字符串,值将是实际字符串。这样,性能不会受到影响,因为您不必为每个比较使用ToLower,但仍然可以使用Contains


0

以下是在整个列表中搜索关键字并删除该项的示例:

public class Book
{
  public int BookId { get; set; }
  public DateTime CreatedDate { get; set; }
  public string Text { get; set; }
  public string Autor { get; set; }
  public string Source { get; set; }
}

如果你想要移除 Text 属性中包含某个关键词的书籍,你可以创建一个关键词列表,并将其从书籍列表中移除:

List<Book> listToSearch = new List<Book>()
   {
        new Book(){
            BookId = 1,
            CreatedDate = new DateTime(2014, 5, 27),
            Text = " test voprivreda...",
            Autor = "abc",
            Source = "SSSS"

        },
        new Book(){
            BookId = 2,
            CreatedDate = new DateTime(2014, 5, 27),
            Text = "here you go...",
            Autor = "bcd",
            Source = "SSSS"


        }
    };

var blackList = new List<string>()
            {
                "test", "b"
            }; 

foreach (var itemtoremove in blackList)
    {
        listToSearch.RemoveAll(p => p.Source.ToLower().Contains(itemtoremove.ToLower()) || p.Source.ToLower().Contains(itemtoremove.ToLower()));
    }


return listToSearch.ToList();

-2

我曾遇到类似的问题,需要获取项目的索引,但不区分大小写。我在网上搜索了几分钟,但没有找到合适的方法,所以我就编写了一个小方法来解决它,具体如下:

private static int getCaseInvariantIndex(List<string> ItemsList, string searchItem)
{
    List<string> lowercaselist = new List<string>();

    foreach (string item in ItemsList)
    {
        lowercaselist.Add(item.ToLower());
    }

    return lowercaselist.IndexOf(searchItem.ToLower());
}

将此代码添加到同一文件中,并像这样调用它:
int index = getCaseInvariantIndexFromList(ListOfItems, itemToFind);

希望这能帮到你,祝你好运!


1
为什么要生成第二个列表?这样并不高效。对于(var i = 0; i < itemsList.Count; i++) { 如果(item.ToLower() == searchItem.ToLower()) { 返回i } } - wesm
我想我们永远不会知道。 - Denny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接