Recherche de liste insensible à la casse

144

J'ai une liste testListqui contient un tas de chaînes. Je voudrais ajouter une nouvelle chaîne dans le testListuniquement si elle n'existe pas déjà dans la liste. Par conséquent, je dois effectuer une recherche insensible à la casse dans la liste et la rendre efficace. Je ne peux pas utiliser Containscar cela ne prend pas en compte le boîtier. Je ne veux pas non plus utiliser ToUpper/ToLowerpour des raisons de performances. Je suis tombé sur cette méthode, qui fonctionne:

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

Cela fonctionne, mais cela correspond également à des mots partiels. Si la liste contient «chèvre», je ne peux pas ajouter «avoine» car il prétend que «avoine» est déjà dans la liste. Existe-t-il un moyen de rechercher efficacement des listes sans tenir compte de la casse, où les mots doivent correspondre exactement? Merci

Brap
la source

Réponses:

181

Au lieu de String.IndexOf, utilisez String.Equals pour vous assurer de ne pas avoir de correspondances partielles. N'utilisez pas non plus FindAll car cela passe par chaque élément, utilisez FindIndex (il s'arrête sur le premier qu'il frappe).

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

Alternativement, utilisez certaines méthodes LINQ (qui s'arrête également sur la première qu'elle frappe)

if( testList.Any( s => s.Equals(keyword, StringComparison.OrdinalIgnoreCase) ) )
    Console.WriteLine("found in list");
Adam Sills
la source
Juste pour ajouter, après quelques tests rapides, il semble que la première méthode soit environ 50% plus rapide. Peut-être que quelqu'un d'autre peut le confirmer / le nier.
Brap
8
À partir de .NET 2.0, cela est maintenant facile à faire - regardez la réponse de shaxby ci-dessous.
Joe
3
Le référencement de la méthode Contains shaxby (qui a une surcharge qui prend un IEqualityComparer) fait partie de LINQ, il n'est donc certainement pas disponible depuis .NET 2.0. Seule la classe StringComparer existe depuis un certain temps. List <T> n'a pas cette méthode, pas plus que ArrayList ou StringCollection (choses qu'il aurait pu facilement référencer comme sa «liste»).
Adam Sills
Eh bien, puisque j'avais réellement besoin de l'indice, c'était certainement la meilleure réponse pour moi.
Nyerguds
1
La première solution doit utiliser la List<>.Exists(Predicate<>)méthode d'instance. Notez également que si la liste contient des nullentrées, cela peut exploser. Dans ce cas, il est plus sûr de dire keyword.Equals(x, StringComparison.OrdinalIgnoreCase)que x.Equals(keyword, StringComparison.OrdinalIgnoreCase)(si vous pouvez garantir que le keywordn'est jamais nul).
Jeppe Stig Nielsen
361

Je me rends compte que c'est un ancien article, mais juste au cas où quelqu'un d'autre le chercherait, vous pouvez l' utiliser Containsen fournissant le comparateur d'égalité de chaîne insensible à la casse comme ceci:

using System.Linq;

// ...

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

Cela est disponible depuis .net 2.0 selon msdn .

minable
la source
21
Certainement la meilleure réponse ici. :)
Joe
23
Enumerable <T> .Contains (ce que vous référencez) n'existe plus depuis .NET 2.0. Il n'y a pas de List <T> .Contains qui a la surcharge que vous utilisez.
Adam Sills
@AdamSills à droite. Il n'existe pas de méthode de ce type dans List <T>. Et s'il s'agit d'une collection paresseuse, elle peut l'itérer plusieurs fois comme le font les autres méthodes Enumerable <T>. Imho, cette méthode ne devrait pas être utilisée pour de tels cas, car ce n'est pas si logique pour ce cas.
Sergey Litvinov
41
Je ne voyais pas cette surcharge au début non plus, mais vous devez ajouter en utilisant System.Linq puis il apparaît.
Michael le
2
La StringComparerclasse existe depuis 2.0, mais cette surcharge de Contains a été introduite dans 3.5. msdn.microsoft.com/en-us/library/bb339118(v=vs.110).aspx
Denise Skidmore
18

Basé sur la réponse d'Adam Sills ci-dessus - voici une belle méthode d'extensions propres pour 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 - MVP Microsoft
la source
10

Vous pouvez utiliser 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
la source
1
Tant que vous ajoutez "using System.Linq", sinon vous ne verrez pas cette surcharge pour .Contains.
Julian Melville
1

Basé sur la réponse de Lance Larsen - voici une méthode d'extension avec la chaîne recommandée.Compare au lieu de string.

Il est fortement recommandé d'utiliser une surcharge de String.Compare qui prend un paramètre StringComparison. Non seulement ces surcharges vous permettent de définir le comportement de comparaison exact que vous vouliez, mais leur utilisation rendra également votre code plus lisible pour les autres développeurs. [ Blog de l'équipe 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
la source
0

Vous vérifiez si le résultat de IndexOf est supérieur ou égal à 0, ce qui signifie si la correspondance commence n'importe où dans la chaîne. Essayez de vérifier s'il est égal à 0:

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

Désormais, «chèvre» et «avoine» ne correspondent pas, mais «chèvre» et «goa» le seront. Pour éviter cela, vous pouvez comparer les longueurs des deux chaînes.

Pour éviter toute cette complication, vous pouvez utiliser un dictionnaire au lieu d'une liste. La clé serait la chaîne minuscule et la valeur la chaîne réelle. De cette façon, les performances ne sont pas affectées car vous n'avez pas à utiliser ToLowerpour chaque comparaison, mais vous pouvez toujours l'utiliser Contains.

Ilya Kogan
la source
0

Voici l'exemple de recherche d'un mot-clé dans toute la liste et de suppression de cet élément:

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 vous souhaitez supprimer un livre contenant un mot clé dans la propriété Text, vous pouvez créer une liste de mots clés et le supprimer de la liste des livres:

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
la source
-1

J'ai eu un problème similaire, j'avais besoin de l'index de l'élément mais il devait être insensible à la casse, j'ai regardé sur le Web pendant quelques minutes et je n'ai rien trouvé, alors j'ai juste écrit une petite méthode pour le faire, voici ce que j'ai fait:

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

Ajoutez ce code au même fichier et appelez-le comme ceci:

int index = getCaseInvariantIndexFromList(ListOfItems, itemToFind);

En espérant que ça aide, bonne chance!

Singe en pyjama
la source
1
pourquoi produire une deuxième liste? Ce n'est pas très efficace. for (var i = 0; i <itemsList.Count; i ++) {if (item.ToLower () == searchItem.ToLower ()) {return i}}
wesm
Je suppose que nous ne saurons jamais.
Denny