Búsqueda de listas sin distinción entre mayúsculas y minúsculas

144

Tengo una lista testListque contiene un montón de cadenas. Me gustaría agregar una nueva cadena a la testListúnica si aún no existe en la lista. Por lo tanto, necesito hacer una búsqueda de mayúsculas y minúsculas en la lista y hacerla eficiente. No puedo usar Containsporque eso no tiene en cuenta la carcasa. Tampoco quiero usar ToUpper/ToLowerpor razones de rendimiento. Encontré este método, que funciona:

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

Esto funciona, pero también coincide con palabras parciales. Si la lista contiene "cabra", no puedo agregar "avena" porque dice que "avena" ya está en la lista. ¿Hay alguna manera de buscar listas de manera eficiente sin distinción de mayúsculas y minúsculas, donde las palabras tienen que coincidir exactamente? Gracias

Brap
fuente

Respuestas:

181

En lugar de String.IndexOf, use String.Equals para asegurarse de que no tiene coincidencias parciales. Tampoco use FindAll ya que eso pasa por cada elemento, use FindIndex (se detiene en el primero que golpea).

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

Alternativamente, use algunos métodos LINQ (que también se detiene en el primero que golpea)

if( testList.Any( s => s.Equals(keyword, StringComparison.OrdinalIgnoreCase) ) )
    Console.WriteLine("found in list");
Adam Sills
fuente
Solo para agregar, en algunas pruebas rápidas, parece que el primer método es aproximadamente un 50% más rápido. Quizás alguien más pueda confirmar / negar eso.
Brap
8
A partir de .NET 2.0, esto ahora se puede hacer fácilmente: mire la respuesta de shaxby a continuación.
Joe
3
El método Contiene la referencia de shaxby (que tiene una sobrecarga que toma un IEqualityComparer) es parte de LINQ, por lo que ciertamente no ha estado disponible desde .NET 2.0. Solo la clase StringComparer ha existido por un tiempo. List <T> no tiene ese método, ni ArrayList o StringCollection (cosas a las que podría haber estado haciendo referencia fácilmente como su 'lista').
Adam Sills
Bueno, como realmente necesitaba el índice, esta fue definitivamente la mejor respuesta para mí.
Nyerguds
1
La primera solución debe usar el List<>.Exists(Predicate<>)método de instancia. También tenga en cuenta que si la lista contiene nullentradas, esto puede explotar. En ese caso, es más seguro decir keyword.Equals(x, StringComparison.OrdinalIgnoreCase)que x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(si puede garantizar que keywordnunca sea nulo).
Jeppe Stig Nielsen
361

Me doy cuenta de que esta es una publicación antigua, pero en caso de que alguien más esté mirando, puede usar Containsproporcionando el comparador de igualdad de cadena que no distingue entre mayúsculas y minúsculas de la siguiente manera:

using System.Linq;

// ...

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

Esto ha estado disponible desde .net 2.0 de acuerdo con msdn .

shaxby
fuente
21
Definitivamente la mejor respuesta aquí. :)
Joe
23
Enumerable <T> .Contains (a lo que hace referencia) no ha existido desde .NET 2.0. No hay una Lista <T> .Contains que tenga la sobrecarga que está utilizando.
Adam Sills
@AdamSills tiene razón. No existe tal método contiene en la Lista <T>. Y si es una colección perezosa, puede iterarla un par de veces como lo hacen otros métodos Enumerable <T>. En mi opinión, este método no debe usarse para tales casos, ya que no es tan lógico para ese caso.
Sergey Litvinov
41
No estaba viendo esta sobrecarga al principio tampoco, pero necesita agregar usando System.Linq luego aparece.
Michael
2
La StringComparerclase ha existido desde 2.0, pero esa sobrecarga de Contains se introdujo en 3.5. msdn.microsoft.com/en-us/library/bb339118(v=vs.110).aspx
Denise Skidmore el
18

Basado en la respuesta anterior de Adam Sills: aquí hay un buen método de extensiones limpias para Contiene ... :)

///----------------------------------------------------------------------
/// <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 - MVP de Microsoft
fuente
10

Puedes 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
fuente
1
Siempre que agregue "usando System.Linq", de lo contrario no verá esa sobrecarga para .Contains.
Julian Melville el
1

Basado en la respuesta de Lance Larsen: aquí hay un método de extensión con la cadena recomendada. Comparar en lugar de cadena.

Se recomienda encarecidamente que utilice una sobrecarga de String.Compare que tome un parámetro StringComparison. Estas sobrecargas no solo le permiten definir el comportamiento exacto de comparación que pretendía, sino que usarlas también hará que su código sea más legible para otros desarrolladores. [ Blog del equipo de 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
fuente
0

Está comprobando si el resultado de IndexOf es mayor o igual a 0, lo que significa si la coincidencia comienza en algún lugar de la cadena. Intenta comprobar si es igual a 0:

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

Ahora "cabra" y "avena" no coincidirán, pero "cabra" y "cabra" sí. Para evitar esto, puede comparar las longitudes de las dos cadenas.

Para evitar toda esta complicación, puede usar un diccionario en lugar de una lista. La clave sería la cadena en minúsculas, y el valor sería la cadena real. De esta manera, el rendimiento no se ve afectado porque no tiene que usarlo ToLowerpara cada comparación, pero aún puede usarlo Contains.

Ilya Kogan
fuente
0

A continuación se muestra el ejemplo de búsqueda de una palabra clave en toda la lista y eliminar ese elemento:

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

Si desea eliminar un libro que contiene alguna palabra clave en la propiedad Texto, puede crear una lista de palabras clave y eliminarla de la lista de libros:

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
fuente
-1

Tuve un problema similar, necesitaba el índice del elemento, pero no debía distinguir entre mayúsculas y minúsculas, busqué en la web durante unos minutos y no encontré nada, así que escribí un pequeño método para hacerlo, esto es lo que hice. hizo:

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

Agregue este código al mismo archivo y llámelo así:

int index = getCaseInvariantIndexFromList(ListOfItems, itemToFind);

Espero que esto ayude, buena suerte!

Mono en pijama
fuente
1
¿Por qué producir una segunda lista? Eso no es muy eficiente. for (var i = 0; i <itemsList.Count; i ++) {if (item.ToLower () == searchItem.ToLower ()) {return i}}
wesm
Supongo que nunca lo sabremos.
Denny