Пошук у невідчутному випадку


144

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

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

Це працює, але воно також відповідає частковим словам. Якщо список містить "козу", я не можу додати "овес", оскільки він стверджує, що "овес" вже є в списку. Чи є спосіб ефективного пошуку списків у нечутливому до випадків випадку, коли слова мають точно відповідати? Дякую

Відповіді:


180

Замість 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% швидший. Можливо, хтось інший може це підтвердити / спростувати.
Бреп

8
Що стосується .NET 2.0, це зараз легко зробити - дивіться відповідь shaxby нижче.
Джо

3
Метод Contains посилання shaxby (що має перевантаження, яке приймає IEqualityComparer) є частиною LINQ, тому він, безумовно, недоступний з .NET 2.0. Просто клас StringComparer вже деякий час існує. У списку <T> немає цього методу, а також у ArrayList або StringCollection (речі, які він міг би легко посилатись на свій "список").
Адам Сілз

Ну, оскільки мені справді потрібен був індекс, це, безумовно, найкраща відповідь для мене.
Nyerguds

1
Перше рішення повинно використовувати List<>.Exists(Predicate<>)метод екземпляра. Також зауважте, що якщо список містить nullзаписи, це може підірватись. У цьому випадку це безпечніше сказати, keyword.Equals(x, StringComparison.OrdinalIgnoreCase)ніж x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(якщо ви можете гарантувати, що значення keywordніколи не буває нульовим).
Jeppe Stig Nielsen

360

Я усвідомлюю, що це стара публікація, але на всякий випадок, коли хтось інший шукає, ви можете скористатися Contains, надавши порівняльному рівню рядків рядки, що не чутливі до випадку:

using System.Linq;

// ...

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

Це доступно з .net 2.0 відповідно до msdn .


21
Однозначно найкраща відповідь тут. :)
Джо

22
Численні <T> .Контейнери (на що ви посилаєтесь) не існували з моменту .NET 2.0. Немає Список <T> .Контент, який би використовував перевантаження.
Адам Підвіконня

@AdamSills правильно. У списку <T> немає такого методу містить. І якщо це лінива колекція, то вона може повторити її в кілька разів, як це роблять інші Численні <T> методи. Імхо, цей метод не слід застосовувати для таких випадків, оскільки це не так логічно для цього випадку.
Сергій Литвинов

40
Я спочатку не бачив цього перевантаження, але вам потрібно додати за допомогою System.Linq, тоді воно з’явиться.
Майкл

1
StringComparerКлас був приблизно з 2,0, але перевантаження Містить була введена в 3.5. msdn.microsoft.com/en-us/library/bb339118(v=vs.110).aspx
Деніз

18

На основі відповіді Адама Силса вище - ось приємний чистий метод розширень для вмісту ... :)

///----------------------------------------------------------------------
/// <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
Поки ви додасте "за допомогою System.Linq", інакше ви не побачите цього перевантаження для .Contains.
Джуліан Мелвілл

1

На основі відповіді Ленса Ларсена - ось метод розширення з рекомендованим рядком. Порівняйте замість string.Equals

Настійно рекомендується використовувати перевантаження String.Compare, що приймає параметр StringComppare. Ці перевантаження не тільки дозволяють визначити точну поведінку порівняння, яку ви задумали, їх використання також зробить ваш код більш читабельним для інших розробників. [ Блог Josh Free @ 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);
}

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
навіщо складати другий список? Це не дуже ефективно. for (var i = 0; i <itemsList.Count; i ++) {if (item.ToLower () == searchItem.ToLower ()) {return i}}
wesm

Я думаю, ми ніколи не дізнаємось.
Денні
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.