Je travaille avec Python et je souhaite faire correspondre une chaîne donnée avec plusieurs sous-chaînes. J'ai essayé de résoudre ce problème de deux manières différentes. Ma première solution a été de faire correspondre la sous-chaîne avec la chaîne comme:
str = "This is a test string from which I want to match multiple substrings" value = ["test", "match", "multiple", "ring"] temp = [] temp.extend([x.upper() for x in value if regex.search(r"\b" + regex.escape(x) + r"\b", str, regex.IGNORECASE) is not None]) print(temp)
ce qui donne temp = ["TEST", "MATCH", "MULTIPLE", "RING"]
Cependant, ce n'est pas le résultat que j'aimerais. Les sous-chaînes doivent avoir une correspondance exacte, donc "ring" ne doit pas correspondre à "string".
C'est pourquoi j'ai essayé de résoudre ce problème avec des expressions régulières, comme ceci:
str = "This is a test string from which I want to match multiple substrings" value = ["test", "match", "multiple", "ring"] temp = [] temp.extend([x.upper() for x in value if x.lower() in str.lower()]) print(temp)
qui aboutit à ["TEST", "MATCH", "MULTIPLE"], la bonne solution. Quoi qu'il en soit, cette solution prend trop de temps à calculer. Je dois faire cette vérification pour environ 1 million de chaînes et la solution utilisant regex prendra des jours pour se terminer par rapport aux 1,5 heures qu'il faut avec la première solution.
Je voudrais savoir s'il existe un moyen de faire fonctionner la première solution ou de faire fonctionner la deuxième solution plus rapidement. Merci d'avance
EDIT: value
peut également contenir des nombres, ou une courte phrase comme "test1 test2"
3 Réponses :
Vous pouvez diviser la chaîne
par un espace, puis faire correspondre les éléments de la valeur
avec ==
EDIT:
Vous avez donc dit que certaines chaînes de valeurs
peuvent avoir un espace avant ou après elles. Vous pouvez résoudre cela avec cette ligne:
str = ['Hi,', 'how', 'are', 'you?']` values = ['how', 'you', 'time', 'space'] new_str = [] for word in str: for j in values: if word.startswith(j): new_str.append(word) # result -> ['how', 'you?']
Cela supprimera tous les espaces avant et après la chaîne (dans votre cas pour chaque élément).
De plus, vous avez mentionné que si vous divisez la chaîne
par un espace, certains mots ont des ponctuations laissées par le fractionnement -> «Salut, comment vas-tu?»
entraînerait ['Salut', 'comment', 'êtes-vous', 'vous?']
. Vous pouvez résoudre ce problème en utilisant la méthode intégrée de chaîne startswith ()
pour filtrer tous les mots commençant par des éléments de valeurs
comme ceci:
values = [i.strip() for i in values]
Ensuite, vous pouvez supprimer les ponctuations de la liste résultante avec des expressions régulières, mais vous aurez maintenant une liste beaucoup plus petite à parcourir. Après avoir supprimé tous les caractères de ponctuation, vous pouvez faire correspondre les chaînes entières comme je l'ai suggéré dans la réponse d'origine.
J'espère que c'est plus clair maintenant.
Ne fonctionne pas si un élément de value
contient un espace, ou si la phrase contient de la ponctuation à côté d'un mot qui autrement serait mis en correspondance.
Eh bien, vous pouvez faire value = [i.strip () for i in value]
et filtrer les éléments de str
en utilisant la méthode de chaîne startswith ()
puis supprimez les ponctuations de la liste filtrée et voyez le résultat
Désolé, je ne suis pas sûr de comprendre. Pouvez-vous modifier un code complet dans votre réponse et montrer comment cela fonctionne?
Merci pour votre modification, mais je pense qu'il me manque encore quelque chose. Lorsque j'exécute ce bloc de code, j'obtiens NameError: le nom 'i' n'est pas défini
.
«Vous avez donc dit que certaines chaînes de valeurs peuvent avoir un espace avant ou après elles. Je ne pense pas que ce soit ce qu'il dit. Je pense qu'il dit que les valeurs peuvent avoir un espace en elles, pas nécessairement au début ou à la fin. "test1 test2", par exemple, contient un espace. Et ce serait une erreur de le supprimer, car "test1test2" ne doit pas être mis en correspondance, et il serait erroné de le diviser en plusieurs éléments, car "test1" ne doit pas être mis en correspondance à moins qu'il ne soit immédiatement avant un espace suivi de "test2" .
Merci pour la clarification du problème, je ne savais pas que c'était le cas. En reclassant votre commentaire sur l'erreur, je l'ai corrigé mais c'était clairement l'erreur de dénomination, vous auriez pu le découvrir vous-même.
Il est difficile de suggérer une solution optimale sans voir les données réelles, mais vous pouvez essayer ces choses:
'^'
ou '*'
). temp
, en évitant les copies inutiles avec temp.extend ()
. import regex # 'str' is a built-in name, so use 'string' instead string = 'This is a Test string from which I want to match multiple substrings' values = ['test', 'test2', 'Multiple', 'ring', 'match'] pattern = r'\b({})\b'.format('|'.join(map(regex.escape, values))) # unique matches, lowercased matches = set(map(str.lower, regex.findall(pattern, string, regex.IGNORECASE))) # arrange the results as they appear in `values` temp = [x.upper() for x in values if x.lower() in matches] print(temp) # ['TEST', 'MULTIPLE', 'MATCH']
Merci pour votre réponse. Cela m'a aidé à résoudre mon problème. Je n'ai eu qu'à ajuster if x in matches
à if x.lower () in matches
et faire de même pour que string
ne soit pas sensible à la casse. J'ai également utilisé temp.extend ()
parce que j'ai d'abord ajouté quelque chose à temp
avant de faire la correspondance.
J'utilise ce code depuis un certain temps maintenant, mais j'ai malheureusement trouvé un problème. Si j'ai string = "test1 test2"
et values = ['test1', 'test1 test2']
, il doit correspondre aux deux instances. Pour le moment, je n'ai que le premier match. Avez-vous peut-être une idée de la façon dont je peux résoudre ce problème?
@ jv3768 Malheureusement, ce n'est pas possible avec une seule recherche, car le moteur regex renvoie la première correspondance valide en sautant d'autres alternatives. Vous pouvez essayer d'analyser les valeurs d'entrée et de filtre qui sont des sous-chaînes d'autres valeurs, puis de les inclure simplement dans les résultats.
Deux optimisations possibles me viennent à l'esprit:
re.compile
pour qu'il ne se recompile pas à chaque fois que vous appelez match
.
['TEST', 'MATCH', 'TEST1 TEST2', 'MULTIPLE']
Résultat:
import re str = "This is a test string from which I want to match test1 test2 multiple substrings" values = ["test", "match", "multiple", "ring", "test1 test2"] pattern = re.compile("|".join(r"\b" + re.escape(x) + r"\b" for x in values)) temp = [] temp.extend([x.upper() for x in pattern.findall(str, re.IGNORECASE)]) print(temp)
Inconvénients potentiels de cette approche:
valeurs
. Cette approche place les résultats dans l'ordre dans lequel ils apparaissent dans str
. temp
si elle apparaissait plusieurs fois dans str
. Contrairement à votre approche d'origine, où la valeur apparaît au plus une fois dans temp
. La
recherche
se termine dès qu'elle trouve une correspondance. findall
recherche toujours la chaîne entière. Si vous vous attendez à ce que la plupart de vos chaînes correspondent à tous les mots de la valeur
, et que la plupart des correspondances apparaissent tôt dans la chaîne, alors findall
peut être plus lent que la recherche
. D'un autre côté, si vous prévoyez que la recherche affichera souvent Aucun
, alors findall
sera probablement un peu plus rapide. «Compiler» le modèle ne fournira aucun avantage ici car vous ne l'utilisez qu'une seule fois. Même s'il était utilisé plusieurs fois, la différence serait très probablement négligeable.
Mon interprétation du problème réel est qu'il y a des millions de chaînes qui doivent être recherchées, plutôt que simplement celle présente dans l'exemple de code. Imaginez qu'il y ait une ligne for str dans millions_of_strings:
juste après mon temp = []
.
Merci pour votre réponse, mais votre premier inconvénient potentiel est un réel inconvénient pour mon code. Je veux donner la priorité au premier match par rapport à tous les autres matchs. Je pense que la réponse d'Eugene résoudra mon problème
@ jv3768 Autant que je sache, la réponse d'Eugene aura également cet inconvénient, car elle utilise également findall.
@Kevin: On peut trier les résultats en fonction de leur idx en valeur
(j'ai ajouté cette étape).
Vous pouvez probablement gagner beaucoup de temps en compilant votre solution et en exécutant les versions compilées sur vos millions de chaînes
@jeremycg qu'entendez-vous exactement par "compiler votre solution"?
mais cela ne fonctionne pas lorsque la valeur contient des sous-chaînes comme "test1 test2"
. Donc, si value a un mot contenu dansstr
, il devrait y avoir une correspondance?en utilisant
re.compile
comme mentionné par @Kevin dans sa réponse