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

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

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

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


person Brap    schedule 16.10.2010    source источник


Ответы (8)


Вместо 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");
person Adam Sills    schedule 16.10.2010
comment
Чтобы добавить, в нескольких быстрых тестах кажется, что первый метод примерно на 50% быстрее. Может быть, кто-нибудь еще сможет подтвердить / опровергнуть это. - person Brap; 16.10.2010
comment
Что касается .NET 2.0, это теперь легко сделать - посмотрите на ответ Шаксби ниже. - person Joe; 29.05.2013
comment
Ссылка Shaxby на метод Contains (имеющая перегрузку, которая принимает IEqualityComparer) является частью LINQ, поэтому она определенно недоступна с .NET 2.0. Просто класс StringComparer существует уже некоторое время. List ‹T› не имеет этого метода, как и ArrayList или StringCollection (вещи, на которые он мог легко сослаться как на свой «список»). - person Adam Sills; 29.05.2013
comment
Ну, поскольку мне действительно нужен индекс, это определенно лучший ответ для меня. - person Nyerguds; 20.01.2014
comment
Первое решение должно использовать List<>.Exists(Predicate<>) метод экземпляра. Также обратите внимание, что если список содержит null записей, это может взорваться. В этом случае более безопасно сказать keyword.Equals(x, StringComparison.OrdinalIgnoreCase), чем x.Equals(keyword, StringComparison.OrdinalIgnoreCase) (если вы можете гарантировать, что keyword никогда не будет нулевым). - person Jeppe Stig Nielsen; 24.02.2015
comment
Боковое примечание ... для массивов вы можете использовать статический Array.FindIndex(array, predicate). - person Nyerguds; 23.06.2018

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

using System.Linq;

// ...

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

Это доступно с .net 2.0 согласно msdn.

person shaxby    schedule 15.03.2013
comment
Определенно лучший ответ здесь. :) - person Joe; 29.05.2013
comment
Enumerable ‹T› .Contains (на что вы ссылаетесь) не было со времен .NET 2.0. Нет списка ‹T›. Содержит перегрузку, которую вы используете. - person Adam Sills; 29.05.2013
comment
@AdamSills, верно. В Списке нет такого метода содержания ‹T›. А если это ленивая коллекция, она может повторить ее несколько раз, как это делают другие методы Enumerable ‹T›. Имхо, этот метод не стоит использовать в таких случаях, потому что он не так логичен для этого случая. - person Sergey Litvinov; 20.11.2013
comment
Если ваш список отсортирован, вы можете использовать BinarySearch (string, IComparable ‹string›), который реализует StringComparer. - person silent tone; 11.02.2014
comment
Сначала я тоже не видел этой перегрузки, но вам нужно добавить using System.Linq, чтобы она появилась. - person Michael; 11.09.2014
comment
@shaxby: Ты только что закончил мои часы головокружения. Миллион благодарностей. - person Syed Ali Taqi; 24.07.2015
comment
Класс StringComparer существует с 2.0, но эта перегрузка Contains появилась в 3.5. msdn.microsoft.com/en-us/library /bb339118(v=vs.110).aspx - person Denise Skidmore; 26.03.2018

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

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

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

    if (list.Contains("MOTH", StringComparer.OrdinalIgnoreCase))
    {
        Console.WriteLine("found");
    }
person jlo-gmail    schedule 26.02.2019
comment
Пока вы добавляете using System.Linq, иначе вы не увидите эту перегрузку для .Contains. - person Julian Melville; 14.11.2019

На основе ответа Ланса Ларсена - вот метод расширения с рекомендуемой строкой.Compare вместо string.Equals

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

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);
}
person dontbyteme    schedule 25.04.2016

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

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

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

Чтобы избежать всех этих сложностей, вы можете использовать словарь вместо списка. Их ключом будет строка в нижнем регистре, а значением будет настоящая строка. Таким образом, производительность не пострадает, потому что вам не нужно использовать ToLower для каждого сравнения, но вы все равно можете использовать Contains.

person Ilya Kogan    schedule 16.10.2010

Ниже приведен пример поиска ключевого слова во всем списке и удаления этого элемента:

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();
person Himanshu Chopra    schedule 16.12.2019

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

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

Надеюсь это поможет. Удачи!

person Monkey in Pajamas    schedule 28.01.2013
comment
зачем составлять второй список? Это не очень эффективно. for (var i = 0; i ‹itemsList.Count; i ++) {if (item.ToLower () == searchItem.ToLower ()) {return i}} - person wesm; 18.12.2014
comment
Думаю, мы никогда не узнаем. - person Denny; 14.06.2017