web-dev-qa-db-fra.com

Vérifier si une chaîne contient un élément d'une liste (de chaînes)

Pour le bloc de code suivant:

For I = 0 To listOfStrings.Count - 1
    If myString.Contains(lstOfStrings.Item(I)) Then
        Return True
    End If
Next
Return False

La sortie est:

Cas 1:

myString: C:\Files\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: True

Cas 2:

myString: C:\Files3\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: False

La liste (listOfStrings) peut contenir plusieurs éléments (minimum 20) et doit être vérifiée par rapport à des milliers de chaînes (comme ma chaîne).

Existe-t-il un meilleur moyen (plus efficace) d'écrire ce code?

127
user57175

Avec LINQ et en utilisant C # (je ne connais pas beaucoup VB de nos jours):

bool b = listOfStrings.Any(s=>myString.Contains(s));

ou (plus court et plus efficace, mais peut-être moins clair):

bool b = listOfStrings.Any(myString.Contains);

Si vous testiez l'égalité, il serait intéressant de regarder HashSet etc.


update: si vous voulez vraiment dire "StartsWith", vous pouvez trier la liste et la placer dans un tableau; Ensuite, utilisez Array.BinarySearch pour trouver chaque élément. Vérifiez par recherche si la correspondance est complète ou partielle.

283
Marc Gravell

lorsque vous construisez vos chaînes, il devrait être comme ça

bool inact = new string[] { "SUSPENDARE", "DIZOLVARE" }.Any(s=>stare.Contains(s));
5
Simi2525

Il y avait un certain nombre de suggestions d'une question similaire précédente " Le meilleur moyen de tester la chaîne existante par rapport à une longue liste de comparables ".

Regex pourrait suffire à vos besoins. L'expression serait une concaténation de toutes les sous-chaînes candidates, avec un opérateur OR "|" entre elles. Bien sûr, vous devrez faire attention aux caractères non échappés lors de la construction de l'expression, ou à un échec de la compilation en raison de contraintes de complexité ou de taille.

Une autre façon de procéder serait de construire une structure de données trie pour représenter toutes les sous-chaînes candidates (cela pourrait en quelque sorte dupliquer ce que fait le matre regex). Au fur et à mesure que vous parcourez chaque caractère de la chaîne de test, vous devez créer un nouveau pointeur sur la racine du test et faire avancer les pointeurs existants sur l'enfant approprié (le cas échéant). Vous obtenez une correspondance lorsque n'importe quel pointeur atteint une feuille.

5
Zach Scrivena

J'aimais la réponse de Marc, mais j'avais besoin de la correspondance de contenu pour être efficace.

C'était la solution:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))
3
WhoIsRich

En fonction de vos habitudes, une amélioration consisterait à utiliser StartsWith au lieu de Contains. StartsWith doit seulement parcourir chaque chaîne jusqu'à ce qu'il trouve la première incompatibilité au lieu de devoir redémarrer la recherche à chaque position du caractère lorsqu'il en trouve une.

En outre, en fonction de vos modèles, il semble que vous puissiez extraire la première partie du chemin de myString, puis inverser la comparaison - en recherchant le chemin de départ de myString dans la liste des chaînes plutôt que l'inverse.

string[] pathComponents = myString.Split( Path.DirectorySeparatorChar );
string startPath = pathComponents[0] + Path.DirectorySeparatorChar;

return listOfStrings.Contains( startPath );

EDIT: Cela serait encore plus rapide en utilisant l’idée HashSet que @Marc Gravell mentionne puisque vous pouvez changer Contains en ContainsKey et la recherche serait O(1) au lieu de O (N). Vous devez vous assurer que les chemins correspondent exactement. Notez qu'il ne s'agit pas d'une solution générale, contrairement à celle de @Marc Gravell, mais qu'elle est adaptée à vos exemples.

Désolé pour l'exemple C #. Je n'ai pas eu assez de café pour traduire en VB.

2
tvanfosson

Avez-vous testé la vitesse?

i.e. Avez-vous créé un échantillon de données et l’avez-vous profilé? Ce n'est peut-être pas aussi grave que vous le pensez.

Cela pourrait également être quelque chose que vous pourriez engendrer dans un fil séparé et donner l'illusion de vitesse!

1
Fortyrunner

Je ne sais pas si c'est plus efficace, mais vous pourriez penser à utiliser à Lambda Expressions .

1
Mark Carpenter

L'inconvénient de la méthode Contains est qu'elle ne permet pas de spécifier le type de comparaison, ce qui est souvent important lors de la comparaison de chaînes. Il est toujours sensible à la culture et à la casse. Donc, je pense que la réponse de WhoIsRich est précieuse, je veux juste montrer une alternative plus simple:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
0
Al Kepp
myList.Any(myString.Contains);
0
WIRN

Vieille question. Mais puisque VB.NET était l'exigence d'origine. En utilisant les mêmes valeurs que la réponse acceptée:

listOfStrings.Any(Function(s) myString.Contains(s))
0
Luis Lavieri

Si la vitesse est critique, vous pouvez rechercher l'algorithme Aho-Corasick pour des ensembles de modèles. 

C'est un trie avec des liens d'échec, c'est-à-dire que la complexité est O (n + m + k), où n est la longueur du texte d'entrée, m la longueur cumulée des motifs et k le nombre de correspondances. Il vous suffit de modifier l’algorithme pour qu’il se termine une fois la première correspondance trouvée.

0
Torsten Marek