Ricerca elenco senza distinzione tra maiuscole e minuscole


144

Ho un elenco testListche contiene un sacco di stringhe. Vorrei aggiungere una nuova stringa nel testListsolo se non esiste già nell'elenco. Pertanto, devo fare una ricerca senza distinzione tra maiuscole e minuscole dell'elenco e renderlo efficiente. Non posso usarlo Containsperché non tiene conto dell'involucro. Inoltre, non voglio utilizzare ToUpper/ToLowerper motivi di prestazioni. Mi sono imbattuto in questo metodo, che funziona:

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

Funziona, ma corrisponde anche a parole parziali. Se l'elenco contiene "capra", non posso aggiungere "avena" perché afferma che "avena" è già presente nell'elenco. Esiste un modo per cercare in modo efficiente gli elenchi in modo insensibile alle maiuscole e minuscole, dove le parole devono corrispondere esattamente? Grazie

Risposte:


180

Invece di String.IndexOf, usa String.Equals per assicurarti di non avere corrispondenze parziali. Inoltre, non utilizzare FindAll in quanto passa attraverso ogni elemento, utilizzare FindIndex (si interrompe sul primo che colpisce).

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

Usa alternativamente alcuni metodi LINQ (che si fermano anche sul primo che colpisce)

if( testList.Any( s => s.Equals(keyword, StringComparison.OrdinalIgnoreCase) ) )
    Console.WriteLine("found in list");

Solo per aggiungere, in alcuni test rapidi, sembra che il primo metodo sia circa il 50% più veloce. Forse qualcun altro può confermarlo / negarlo.
Brap

8
A partire da .NET 2.0, ora è facile: guarda la risposta di Shaxby di seguito.
Joe,

3
Il metodo Contiene il riferimento di shaxby (che ha un sovraccarico che richiede un IEqualityComparer) fa parte di LINQ, quindi certamente non è disponibile da .NET 2.0. Solo la classe StringComparer è in circolazione da un po '. List <T> non ha quel metodo, né ArrayList o StringCollection (cose che avrebbe potuto facilmente fare riferimento come suo 'elenco').
Adam Sills,

Bene, dato che avevo davvero bisogno dell'indice, questa è stata sicuramente la risposta migliore per me.
Nyerguds,

1
La prima soluzione dovrebbe usare il List<>.Exists(Predicate<>)metodo di istanza. Si noti inoltre che se l'elenco contiene nullvoci, ciò può saltare in aria. In tal caso è più sicuro dirlo keyword.Equals(x, StringComparison.OrdinalIgnoreCase)che x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(se puoi garantire che keywordnon sia mai nullo).
Jeppe Stig Nielsen,

360

Mi rendo conto che questo è un vecchio post, ma nel caso in cui qualcun altro lo stia guardando, puoi usarlo Containsfornendo il comparatore di uguaglianza di stringa insensibile al case in questo modo:

using System.Linq;

// ...

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

Questo è disponibile da .net 2.0 secondo msdn .


21
Sicuramente la migliore risposta qui. :)
Joe,

23
Enumerabile <T> .Contains (quello a cui stai facendo riferimento) non esiste da .NET 2.0. Non esiste alcun elenco <T> .Contiene il sovraccarico che stai utilizzando.
Adam Sills,

@AdamSills giusto. Non esiste tale metodo contiene nell'elenco <T>. E se è una raccolta pigra, allora può iterarla un paio di volte come fanno altri metodi Enumerable <T>. Imho, questo metodo non dovrebbe essere usato per questi casi, in quanto non è così logico per quel caso.
Sergey Litvinov,

40
All'inizio non vedevo neanche questo sovraccarico, ma è necessario aggiungere utilizzando System.Linq quindi appare.
Michael,

1
La StringComparerclasse esiste da 2.0, ma quel sovraccarico di Contains è stato introdotto in 3.5. msdn.microsoft.com/en-us/library/bb339118(v=vs.110).aspx
Denise Skidmore

18

Basato sulla risposta di Adam Sills sopra - ecco un bel metodo di estensioni pulite per 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);
}

10

Puoi usare 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
Finché aggiungi "using System.Linq", altrimenti non vedrai quel sovraccarico per .Contains.
Julian Melville,

1

Basato sulla risposta di Lance Larsen: ecco un metodo di estensione con la stringa consigliata.Compara invece di string.Equals

Si consiglia vivamente di utilizzare un sovraccarico di String.Compare che accetta un parametro StringComparison. Questi sovraccarichi non solo ti consentono di definire l'esatto comportamento di confronto che intendevi, ma anche l'uso di questi renderà il tuo codice più leggibile per altri sviluppatori. [ Josh Free @ 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);
}

0

Stai verificando se il risultato di IndexOf è maggiore o uguale a 0, ovvero se la corrispondenza inizia in qualsiasi punto della stringa. Prova a verificare se è uguale a 0:

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

Ora "capra" e "avena" non combaciano, ma "capra" e "goa" lo faranno. Per evitare ciò, è possibile confrontare le lunghezze delle due stringhe.

Per evitare tutte queste complicazioni, è possibile utilizzare un dizionario anziché un elenco. La chiave sarebbe la stringa minuscola e il valore sarebbe la stringa reale. In questo modo, le prestazioni non sono danneggiate perché non è necessario utilizzare ToLowerper ogni confronto, ma è comunque possibile utilizzare Contains.


0

Di seguito è riportato l'esempio della ricerca di una parola chiave in tutto l'elenco e rimuovere l'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; }
}

Se desideri rimuovere un libro che contiene alcune parole chiave nella proprietà Text, puoi creare un elenco di parole chiave e rimuoverlo dall'elenco di libri:

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

Avevo un problema simile, avevo bisogno dell'indice dell'articolo ma doveva essere insensibile alle maiuscole, mi sono guardato attorno per qualche minuto e non ho trovato nulla, quindi ho appena scritto un piccolo metodo per farlo, ecco cosa ha fatto:

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

Aggiungi questo codice allo stesso file e chiamalo così:

int index = getCaseInvariantIndexFromList(ListOfItems, itemToFind);

Spero che questo ti aiuti, buona fortuna!


1
perché produrre un secondo elenco? Non è molto efficiente. for (var i = 0; i <itemsList.Count; i ++) {if (item.ToLower () == searchItem.ToLower ()) {return i}}
wesm

Immagino che non lo sapremo mai.
Denny,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.