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

144

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

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

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

Brap
источник

Ответы:

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 .

shaxby
источник
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);
}
Ланс Ларсен - Microsoft MVP
источник
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");
    }
JLo-Gmail
источник
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);
}
dontbyteme
источник
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
Я думаю, мы никогда не узнаем.
Денни