web-dev-qa-db-fra.com

Trouver la dernière correspondance avec une expression régulière en python

Je veux faire correspondre la dernière occurrence d'un motif simple dans une chaîne, par exemple.

list = re.findall(r"\w+ AAAA \w+", "foo bar AAAA foo2 AAAA bar2")
print "last match: ", list[len(list)-1]

cependant, si la chaîne est very long, une liste énorme de correspondances est générée. Existe-t-il un moyen plus direct de faire correspondre la deuxième occurrence de "AAAA" ou dois-je utiliser cette solution de contournement?

22
SDD

vous pouvez utiliser $ qui indique le caractère de fin de ligne:

>>> s = """foo bar AAAA
foo2 AAAA bar2"""
>>> re.findall(r"\w+ AAAA \w+$", s)
['foo2 AAAA bar2']

En outre, notez que list est un nom incorrect pour votre variable, car il ombrage le type intégré. Pour accéder au dernier élément d'une liste, vous pouvez simplement utiliser [-1] index:

>>> lst = [2, 3, 4]
>>> lst[-1]
4
27
SilentGhost

Vous pouvez éviter de créer une liste simplement en parcourant tous les matchs et en conservant le dernier match:

for match in re.finditer(r"\w+ AAAA \w+", "foo bar AAAA foo2 AAAA bar2"):
    pass

Ensuite, match conserve la dernière correspondance et fonctionne pour toutes les combinaisons de pattern et chaînes recherchées . Vous voudrez peut-être définir d'abord match sur None, car s'il n'y a pas de correspondance, match ne sera défini sur aucune valeur.

23
tzot

Je n'étais pas sûr si votre regex originale vous donnerait ce que vous vouliez. Désolé si je suis en retard pour faire la fête .. Mais cela peut aussi être utile pour d’autres.

import re
p = r"AAAA(?=\s\w+)" #revised per comment from @Jerry
p2 =r"\w+ AAAA \w+"
s = "foo bar AAAA foo2 AAAA bar2"
l = re.findall(p, s)
l2 = re.findall(p2, s)
print('l: {l}'.format(l=l))

#print(f'l: {l}') is nicer, but online interpreters sometimes don't support it.
# https://www.onlinegdb.com/online_python_interpreter
#I'm using Python 3.

print('l2: {l}'.format(l=l2))
for m in re.finditer(p, s):
  print(m.span())
  #A span of (n,m) would really represent characters n to m-1 with zero based index
  #So.(8,12):
  # => (8,11: 0 based index)
  # => (9th to 12th characters conventional 1 based index)
print(re.findall(p, s)[-1])

Les sorties:

l: ['AAAA', 'AAAA']
l2: ['bar AAAA foo2']
(8, 12)
(18, 22)   
AAAA

La raison pour laquelle vous obtenez deux résultats ici au lieu d'un dans l'original est la sauce spéciale (?=).

Cela s'appelle un lookahead positif. Cela ne fait pas 'consommer' (c'est-à-dire que le curseur avance), lorsque la correspondance est trouvée lors de l'évaluation de la regex. Donc, il revient après l'appariement.

Bien que les points de référence positifs figurent entre parenthèses, ils agissent également comme un groupe non capturé.

Ainsi, même si un motif correspond, les résultats omit la séquence environnante de caractères alphanumériques représentée par le \w+ et les espaces intermédiaires, \s dans mon exemple -representing [ \t\n\r\f\v]. (Plus ici )

Donc, je ne récupère que AAAA à chaque fois.

p2 représente le motif original du code de @SDD, la personne qui pose la question.

foo2 est consommé, avec ce modèle afin que le second AAAA ne corresponde pas, car le curseur était trop avancé, lorsque le moteur des expressions rationnelles reprend à sa deuxième itération de correspondance.


Je vous recommande de regarder les vidéos Youtube de Moondra si vous voulez creuser plus profondément.

Il a réalisé une série très complète de 17 parties sur Python Regex, débutant ici


Voici un lien vers un interpréteur Python en ligne)

2
JGFMK

Il n'y a pas de fonctionnalité de bibliothèque re intégrée qui prend en charge l'analyse syntaxique de chaîne de droite à gauche. La chaîne d'entrée est uniquement recherchée pour un motif de gauche à droite.

Il existe cependant un module PyPi regex ​​_ qui prend en charge cette fonctionnalité. Il s'agit de l'indicateur regex.REVERSE ou de sa variante intégrée, (?r):

s="foo bar AAAA foo2 AAAA bar2"
print(regex.search(r"(?r)\w+ AAAA \w+$", s).group())
# => foo2 AAAA bar2

Avec le module re, il est possible d’atteindre rapidement la fin de la chaîne à l’aide de la construction ^[\s\S]* et de laisser le retour en arrière rechercher le motif que vous souhaitez intégrer dans un groupe séparé. Toutefois, le retour en arrière peut engloutir une partie de la correspondance (car il cessera de produire plus de texte une fois que tous les modèles suivants auront été mis en correspondance). Si le texte est trop volumineux et qu’il n’ya pas de correspondance, le retour en arrière peut devenir catastrophique. Utilisez cette astuce uniquement si votre chaîne d'entrée correspond toujours, ou si elle est courte et que le modèle personnalisé ne s'appuie pas beaucoup sur le retour arrière:

print(re.search(r"(?:^[\s\S]*\W)?(\w+ AAAA \w+)$", s).group(1))
# => foo2 AAAA bar2

Ici, (?:^[\s\S]*\W)? correspond à une séquence facultative d'un début de chaîne, tout caractère 0 ou plus suivi d'un caractère autre que Word (\W). Il est nécessaire d'ajouter \W pour que le retour en arrière soit renvoyé au caractère autre que Word. Ce caractère doit être facultatif, car la correspondance peut commencer au début de la chaîne.

Voir la démo Python .

1
Wiktor Stribiżew

Un autre moyen rapide consiste à utiliser search et group:

>>> re.search('\w+ AAAA \w+$',"foo bar AAAA foo2 AAAA bar2").group(0)
'foo2 AAAA bar2'

Ce qu'il fait:

  1. Il utilise le modèle \w+ AAAA \w+$, qui obtient la dernière occurrence de 'AAAA' avec ses mots adjacents, tous utilisant \w+ (deux fois) et $ (une fois).

  2. Une fois le filtrage effectué, vous devrez utiliser la méthode _sre.SRE_Match.group pour obtenir la valeur d’appartenance de l’objet _sre.SRE_Match et, bien sûr, le groupe zeroth (premier), sachant que search ne conserve qu’une correspondance (le zeroth).

Voici le regex101 de celui-ci.

Voici le minutage de toutes les réponses (à l'exception de la réponse de JGFMK, car elle est difficile):

>>> timeit.timeit(lambda: re.findall(r"\w+ AAAA \w+$", s),number=1000000) # SilentGhost
5.783595023876842
>>> timeit.timeit('import re\nfor match in re.finditer(r"\w+ AAAA \w+", "foo bar AAAA foo2 AAAA bar2"):pass',number=1000000) # tzot
5.329235373691631
>>> timeit.timeit(lambda: re.search('\w+ AAAA \w+$',"foo bar AAAA foo2 AAAA bar2").group(0),number=1000000) # mine (U9-Forward)
5.441731174121287
>>> 

Je teste tous les timings en utilisant le module timeit, et je fabrique également number=1000000, donc cela prend beaucoup plus de temps.

0
U9-Forward