Vérifiez 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:

Affaire 1:

myString: C:Filesmyfile.doc
listOfString: C:Files, C:Files2
Result: True

Affaire 2:

myString: C:Files3myfile.doc
listOfString: C:Files, C:Files2
Result: False

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

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

120
demandé sur Dukeling 2009-02-01 17:37:50

9 réponses

Avec LINQ, et en utilisant C# (Je ne connais pas beaucoup VB ces jours-ci):

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

Ou (plus court et plus efficace, mais sans doute moins clair):

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

Si vous testiez l'égalité, cela vaudrait la peine de regarder HashSet etc, mais cela n'aidera pas les correspondances partielles sauf si vous le divisez en fragments et ajoutez un ordre de complexité.


Update: si vous voulez vraiment dire "StartsWith", alors vous pouvez trier la liste et la placer dans un tableau; puis utilisez Array.BinarySearch pour trouver chaque élément-vérifier par recherche pour voir s'il s'agit d'une correspondance complète ou partielle.

265
répondu Marc Gravell 2009-02-01 20:35:32

Il y avait un certain nombre de suggestions d'une question similaire antérieure "Meilleure Façon de tester la chaîne existante par rapport à une grande liste de comparables".

Regex peut être suffisant pour 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 la complexité ou de la taille limitation.

Une autre façon de le faire serait de construire une structure de données trie pour représenter toutes les sous-chaînes candidates(cela peut dupliquer quelque peu ce que fait le matcher regex). Lorsque vous parcourez chaque caractère de la chaîne de test, vous créez un nouveau pointeur vers la racine du trie et avancez les pointeurs existants vers l'enfant approprié (le cas échéant). Vous obtenez une correspondance quand un pointeur atteint une feuille.

5
répondu Zach Scrivena 2017-05-23 11:47:05

Lorsque vous construisez vos chaînes, cela devrait être comme ceci

bool inact = new string[] { "SUSPENDARE", "DIZOLVARE" }.Any(s=>stare.Contains(s));
3
répondu Simi2525 2014-12-19 09:19:35

J'ai aimé la réponse de Marc, mais j'avais besoin que la correspondance Contains soit insensible à la casse.

C'était la solution:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))
3
répondu WhoIsRich 2018-07-05 19:41:36

En fonction de vos modèles, une amélioration serait de passer à l'utilisation de StartsWith au lieu de Contains. StartsWith n'a besoin que d'itérer à travers chaque chaîne jusqu'à ce qu'elle trouve la première discordance au lieu d'avoir à redémarrer la recherche à chaque position de caractère lorsqu'elle en trouve une.

En outre, en fonction de vos modèles, il semble que vous puissiez extraire la première partie du chemin pour 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 : ce serait encore plus rapide en utilisant le HashSet idea @ Marc Gravell mentionne puisque vous pourriez 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 que ce n'est pas une solution générale comme celle de @Marc Gravell mais est adaptée à vos exemples.

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

2
répondu tvanfosson 2009-02-01 15:17:23

Je ne suis pas sûr si c'est plus efficace, mais vous pourriez penser à utiliser at Lambda Expressions.

1
répondu Mark Carpenter 2009-02-01 14:44:21

Avez-vous testé la vitesse?

C'est-à-dire Avez-vous créé un échantillon de données et l'avez-vous profilé? Il peut ne pas être aussi mauvais que vous le pensez.

Cela pourrait aussi être quelque chose que vous pourriez générer dans un thread séparé et donner l'illusion de la vitesse!

1
répondu Fortyrunner 2009-02-01 20:50:28

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

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 cumulative des motifs et k le nombre de correspondances. Il vous suffit de modifier l'algorithme pour terminer après la première correspondance.

0
répondu Torsten Marek 2010-12-16 16:22:30
myList.Any(myString.Contains);
0
répondu WIRN 2014-04-25 08:15:38