Pesquisa por lista que não diferencia maiúsculas de minúsculas

144

Eu tenho uma lista testListque contém um monte de seqüências de caracteres. Eu gostaria de adicionar uma nova string testListsomente se ela ainda não existir na lista. Portanto, preciso fazer uma pesquisa sem distinção entre maiúsculas e minúsculas e torná-la eficiente. Não posso usar Containsporque isso não leva em conta a caixa. Eu também não quero usar ToUpper/ToLowerpor motivos de desempenho. Me deparei com este método, que funciona:

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

Isso funciona, mas também corresponde a palavras parciais. Se a lista contiver "cabra", não será possível adicionar "aveia" porque ela afirma que "aveia" já está na lista. Existe uma maneira de pesquisar listas de maneira eficiente, sem distinção entre maiúsculas e minúsculas, onde as palavras precisam corresponder exatamente? obrigado

Brap
fonte

Respostas:

180

Em vez de String.IndexOf, use String.Equals para garantir que você não tenha correspondências parciais. Além disso, não use FindAll, pois ele percorre todos os elementos; use FindIndex (ele pára no primeiro item ).

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

Como alternativa, use alguns métodos LINQ (que também param no primeiro método)

if( testList.Any( s => s.Equals(keyword, StringComparison.OrdinalIgnoreCase) ) )
    Console.WriteLine("found in list");
Adam Sills
fonte
Apenas para adicionar, em alguns testes rápidos, parece que o primeiro método é cerca de 50% mais rápido. Talvez alguém possa confirmar / negar isso.
Brap
8
A partir do .NET 2.0, isso agora é feito com facilidade - veja a resposta de shaxby abaixo.
28513 Joe
3
A referência do método Contains ao shaxby (que possui uma sobrecarga que leva um IEqualityComparer) faz parte do LINQ, portanto, certamente não está disponível desde o .NET 2.0. Apenas a classe StringComparer existe há algum tempo. List <T> não possui esse método, nem ArrayList ou StringCollection (coisas que ele poderia facilmente referenciar como sua 'lista').
Adam Sills
Bem, como eu realmente precisava do índice, essa foi definitivamente a melhor resposta para mim.
Nyerguds
1
A primeira solução deve usar o List<>.Exists(Predicate<>)método de instância. Observe também que, se a lista contiver nullentradas, isso poderá explodir. Nesse caso, é mais seguro dizer keyword.Equals(x, StringComparison.OrdinalIgnoreCase)que x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(se você pode garantir que keywordnunca é nulo).
Jeppe Stig Nielsen
360

Sei que este é um post antigo, mas, caso alguém esteja procurando, você pode usar Containsfornecendo o comparador de igualdade de cadeias sem distinção entre maiúsculas e minúsculas da seguinte maneira:

using System.Linq;

// ...

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

Está disponível desde .net 2.0 de acordo com o msdn .

shaxby
fonte
21
Definitivamente a melhor resposta aqui. :)
Joe
22
Enumerável <T> .Contains (o que você está referenciando) não existe desde o .NET 2.0. Não existe uma lista <T>. Contém a sobrecarga que você está usando.
Adam Sills
@AdamSills certo. Não existe esse método na Lista <T>. E se for uma coleção lenta, pode iterá-la algumas vezes, como outros métodos Enumerable <T>. Imho, esse método não deve ser usado nesses casos, pois não é tão lógico para esse caso.
21813 Sergey Litvinov
40
Também não estava vendo essa sobrecarga no começo, mas você precisa adicionar o System.Linq para que ele apareça.
Michael
1
A StringComparerclasse existe desde a versão 2.0, mas essa sobrecarga de Contains foi introduzida na 3.5. msdn.microsoft.com/pt-br/library/bb339118(v=vs.110).aspx
Denise Skidmore
18

Baseado na resposta de Adam Sills acima - aqui está um bom método de extensões limpas para 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);
}
Lance Larsen - Microsoft MVP
fonte
10

Você pode usar 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
fonte
1
Contanto que você adicione "using System.Linq", caso contrário, você não verá essa sobrecarga para .Contains.
Julian Melville
1

Com base na resposta de Lance Larsen - aqui está um método de extensão com a string recomendada. Compare em vez de string.

É altamente recomendável que você use uma sobrecarga de String.Compare que use um parâmetro StringComparison. Essas sobrecargas não apenas permitem definir o comportamento exato da comparação que você pretendia, mas usá-las também tornará seu código mais legível para outros desenvolvedores. [ Blog da equipe Josh Free @ BCL ]

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
fonte
0

Você está verificando se o resultado de IndexOf é maior ou igual a 0, significando se a correspondência começa em qualquer lugar da string. Tente verificar se é igual a 0:

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

Agora "cabra" e "aveia" não corresponderão, mas "cabra" e "goa" corresponderão. Para evitar isso, você pode comparar os comprimentos das duas cadeias.

Para evitar toda essa complicação, você pode usar um dicionário em vez de uma lista. A chave seria a sequência minúscula e o valor seria a sequência real. Dessa forma, o desempenho não é prejudicado porque você não precisa usar ToLowerpara cada comparação, mas ainda pode usá-lo Contains.

Ilya Kogan
fonte
0

Abaixo está o exemplo de pesquisa por uma palavra-chave em toda a lista e remova esse item:

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

Se você deseja remover um livro que contenha alguma palavra-chave na propriedade Texto, crie uma lista de palavras-chave e remova-a da lista de livros:

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();
Himanshu Chopra
fonte
-1

Eu tive um problema semelhante, precisava do índice do item, mas ele não diferenciava maiúsculas de minúsculas, procurei na web por alguns minutos e não encontrei nada, então acabei de escrever um pequeno método para fazê-lo, eis o que fez:

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

Adicione este código ao mesmo arquivo e chame-o assim:

int index = getCaseInvariantIndexFromList(ListOfItems, itemToFind);

Espero que isso ajude, boa sorte!

Macaco de pijama
fonte
1
por que produzir uma segunda lista? Isso não é muito eficiente. for (var i = 0; i <itemsList.Count; i ++) {if (item.ToLower () == searchItem.ToLower ()) {return i}}
wesm
Acho que nunca saberemos.
Denny