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


156

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 elle doit être comparée à des milliers de chaînes (comme myString).

Existe-t-il une meilleure façon (plus efficace) d'écrire ce code?

Réponses:


360

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 testez l'égalité, cela vaudrait la peine de regarder HashSet, etc., mais cela n'aidera pas les correspondances partielles à moins que vous ne la coupiez en fragments et ajoutiez un ordre de complexité.


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


1
Au lieu de Contains, j'utiliserais StartsWith en fonction de ses exemples.
tvanfosson le

@tvanfosson - cela dépend de la question de savoir si les exemples sont pleinement inclusifs, mais oui, je suis d'accord. Simple à changer, bien sûr.
Marc Gravell

Dans quelle mesure ce code est-il plus efficace au niveau algorithmique? C'est plus court et plus rapide si les boucles dans "Any" sont plus rapides, mais le problème que vous devez effectuer plusieurs fois de correspondance exacte est le même.
Torsten Marek

Vous pouvez configurer un comparateur personnalisé si vous utilisez un ensemble.
Fortyrunner

Le second n'est pas vraiment plus efficace par aucune différence mesurable dans la pratique.
ICR

7

quand vous construisez vos chaînes, cela devrait être comme ça

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

5

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

Regex pourrait ê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 des limitations de taille.

Une autre façon de faire cela serait de construire une structure de données trie pour représenter toutes les sous-chaînes candidates (cela peut en quelque sorte dupliquer ce que fait le matcher regex). Au fur et à mesure que 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 lorsqu'un pointeur atteint une feuille.


5

J'ai aimé la réponse de Marc, mais j'avais besoin de la correspondance Contains pour être CaSe InSenSiTiVe.

C'était la solution:

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

Ne devrait-il pas être> -1?
Charpé le

1
@CSharped Peu importe car> -1 (plus de moins 1) et> = 0 (plus ou égal à zéro) sont la même chose.
WhoIsRich

2

En fonction de vos modèles, une amélioration serait de passer à l'utilisation de StartsWith au lieu de Contains. StartsWith n'a qu'à parcourir chaque chaîne jusqu'à ce qu'il trouve la première discordance au lieu d'avoir à redémarrer la recherche à chaque position de 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 : Ce serait encore plus rapide en utilisant l'idée de HashSet @Marc Gravell mentionne puisque vous pouvez changer Containsà ContainsKeyet la recherche serait O (1) au lieu de O (N). Vous devrez vous assurer que les chemins correspondent exactement. Notez que ce n'est pas une solution générale comme 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.


Re commence par; peut-être pré-trier et utiliser la recherche binaire? Cela pourrait être encore plus rapide.
Marc Gravell

2

Ancienne question. Mais depuis VB.NETétait l'exigence initiale. En utilisant les mêmes valeurs de la réponse acceptée:

listOfStrings.Any(Function(s) myString.Contains(s))

1

Avez-vous testé la vitesse?

c.-à-d. 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 aussi être quelque chose que vous pourriez créer dans un fil séparé et donner l'illusion de la vitesse!


0

Si la vitesse est critique, vous voudrez peut-être rechercher l' algorithme Aho-Corasick pour les ensembles de modèles.

C'est un essai 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 s'arrête une fois la première correspondance trouvée.



0

L'inconvénient de la Containsméthode est qu'elle ne permet pas de spécifier le type de comparaison qui est souvent important lors de la comparaison de chaînes. Il est toujours sensible à la culture et à la casse. Je pense donc 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))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.