Поиск списка без учета регистра


144

У меня есть список, testListкоторый содержит кучу строк. Я хотел бы добавить новую строку в testListединственную, если она еще не существует в списке. Поэтому мне нужно выполнить поиск в списке без учета регистра и сделать его эффективным. Я не могу использовать, Containsпотому что это не принимает во внимание корпус. Я также не хочу использовать ToUpper/ToLowerпо причинам производительности. Я сталкивался с этим методом, который работает:

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

Это работает, но также соответствует частичным словам. Если список содержит «козел», я не могу добавить «овес», потому что он утверждает, что «овес» уже есть в списке. Есть ли способ эффективно искать списки без учета регистра, где слова должны точно совпадать? Спасибо

Ответы:


181

Вместо 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

8
Начиная с .NET 2.0 это теперь легко сделать - посмотрите ответ Shaxby ниже.
— Джо

3
Ссылка метода Contains Shaxby (которая имеет перегрузку, которая принимает IEqualityComparer) является частью LINQ, поэтому она, безусловно, не была доступна с .NET 2.0. Просто класс StringComparer существует уже некоторое время. List <T> не имеет такого метода, как и ArrayList или StringCollection (вещи, на которые он мог бы легко ссылаться как на свой «список»).
— Адам Силлс

Ну, так как мне действительно нужен индекс, это был определенно лучший ответ для меня.
— Nyerguds

1
Первое решение должно использовать List<>.Exists(Predicate<>)метод экземпляра. Также обратите внимание, что если список содержит nullзаписи, это может взорвать. В этом случае это более безопасно, keyword.Equals(x, StringComparison.OrdinalIgnoreCase)чем сказать x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(если вы можете гарантировать, что keywordникогда не будет нулевым).
— Джеппе Стиг Нильсен,

361

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

using System.Linq;

// ...

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

Это было доступно начиная с .net 2.0 в соответствии с MSDN .


21
Определенно лучший ответ здесь. :)
— Джо

23
Enumerable <T> .Contains (то, на что вы ссылаетесь) не существовал с .NET 2.0. Список <T> отсутствует. Содержит перегрузку, которую вы используете.
— Адам Силлс

@ AdamSills верно. В List <T> такого метода нет. И если это ленивая коллекция, то она может повторить ее пару раз, как это делают другие методы Enumerable <T>. Имхо, этот метод не должен использоваться для таких случаев, так как он не так логичен для этого случая.
— Сергей Литвинов

41
Я также не видел эту перегрузку сначала, но вам нужно добавить с помощью System.Linq, а затем появляется.
— Майкл

2
StringComparerКласс был примерно с 2,0, но перегрузка Содержит была введена в 3.5. msdn.microsoft.com/en-us/library/bb339118(v=vs.110).aspx
— Дениз Скидмор

18

Основываясь на ответе Адама Силлса выше - вот хороший чистый метод расширений для 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);
}

10

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

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

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

1
Пока вы добавляете «using System.Linq», в противном случае вы не увидите эту перегрузку для .Contains.
— Джулиан Мелвилл

1

Основано на ответе Ланса Ларсена - вот метод расширения с рекомендуемой строкой. Сравните вместо строки.

Настоятельно рекомендуется использовать перегрузку String.Compare, которая принимает параметр StringComparison. Эти перегрузки не только позволяют вам определить точное поведение при сравнении, но и сделать ваш код более читабельным для других разработчиков. [ Джош Фри @ BCL Team Blog ]

public static bool Contains(this List<string> source, string toCheck, StringComparison comp)
{
    return
       source != null &&
       !string.IsNullOrEmpty(toCheck) &&
       source.Any(x => string.Compare(x, toCheck, comp) == 0);
}

0

Вы проверяете, является ли результат IndexOf большим или равным 0, то есть начинается ли совпадение где-либо в строке. Попробуйте проверить, равно ли оно 0:

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

Теперь «коза» и «овес» не будут совпадать, но «коза» и «козлята» будут совпадать. Чтобы избежать этого, вы можете сравнить длины двух строк.

Чтобы избежать всего этого, вы можете использовать словарь вместо списка. Их ключом будет строка нижнего регистра, а значением будет настоящая строка. Таким образом, производительность не пострадает, потому что вам не нужно использовать 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();

-1

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

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
зачем составлять второй список? Это не очень эффективно. для (вар я = 0; I <itemsList.Count; я ++) {если (item.ToLower () == searchItem.ToLower ()) {вернуться я}}
— wesm

Я думаю, мы никогда не узнаем.
— Денни
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.