Encuentra la última coincidencia con la expresión regular de Python

Quiero coincidir con la última aparición de un patrón simple en una cadena, por ejemplo

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

sin embargo, si la cadena es muy larga, se genera una enorme lista de coincidencias. ¿Hay una forma más directa de hacer coincidir la segunda aparición de “AAAA” o debo usar esta solución?

podría usar $ que denota el final de la línea de caracteres:

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

Además, tenga en cuenta que la list es un mal nombre para su variable, ya que oculta el tipo incorporado. Para acceder al último elemento de una lista, puede usar el índice [-1] :

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

Puede evitar la creación de una lista con solo iterar todas las coincidencias y mantener la última coincidencia:

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

Después de esto, la match mantiene la última coincidencia y funciona para todas las combinaciones de patrones y cadenas buscadas . Es posible que desee establecer la match en None primero, porque si no hay coincidencia, la match no se establecerá en ningún valor.

No estaba seguro de si su expresión regular original le daría lo que quería. Lo siento si llego tarde a la fiesta … Pero a otros también les puede resultar útil.

 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]) 

Salidas:

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

La razón por la que obtiene dos resultados aquí en lugar de uno en original es la salsa especial (?=) .

Se llama un lookahead positivo. No ‘ consume ‘ (es decir, avanza el cursor), cuando se encuentra la coincidencia durante la evaluación de expresiones regulares. Por lo tanto, vuelve después de emparejar.

Aunque los parentescos positivos están entre paréntesis, también actúan como un grupo que no captura .

Entonces, aunque un patrón coincide, los resultados omiten la secuencia circundante de caracteres alfanuméricos representados por \w+ y los espacios intermedios, \s en mi ejemplo -representando [ \t\n\r\f\v] . (Más aquí )

Así que solo obtengo AAAA cada vez.

Aquí, p2 representa el patrón original del código de @SDD, la persona que plantea la pregunta.

foo2 se consume , con ese patrón por lo que la segunda AAAA no coincidiría, ya que el cursor había avanzado demasiado, cuando el motor de expresiones regulares se reinicia en su segunda iteración de coincidencia.


Recomiendo echar un vistazo a los videos de Youtube de Moondra si quieres profundizar más.

Él ha hecho una serie muy completa de 17 partes en Python Regex, comenzando aquí.


Aquí hay un enlace a un intérprete de Python en línea

No hay una función de re biblioteca incorporada que admita el análisis de cadena de derecha a izquierda, la cadena de entrada solo busca un patrón de izquierda a derecha.

Sin embargo, hay un módulo de expresiones regulares de PyPi que admite esta función. Es el indicador regex.REVERSE , o su variación en línea, (?r) :

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

Con re modulo, hay una manera de llegar rápidamente al final de la cadena usando ^[\s\S]* construyendo y dejando que el rastreo retroceda para encontrar el patrón que le gustaría incluir en un grupo separado. Sin embargo, el retroceso puede engullir parte de la coincidencia (ya que dejará de producir más texto una vez que todos los patrones subsiguientes coincidan), y en caso de que el texto sea demasiado grande y no haya coincidencia, el retroceso puede volverse catastrófico. Solo use este truco si su cadena de entrada siempre coincide, o si es corta y el patrón personalizado no depende mucho del retroceso:

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

Aquí, (?:^[\s\S]*\W)? coincide con una secuencia opcional de un inicio de cadena, cualquier 0 o más caracteres seguidos de un carácter sin palabra ( \W ). Es necesario agregar \W para que el retroceso vuelva al carácter sin palabra, y debe ser opcional ya que la coincidencia podría comenzar al principio de la cadena.

Vea la demo de Python .

Otra forma rápida es usando la search y el group :

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

Que hace:

  1. Utiliza el patrón de \w+ AAAA \w+$ , que es la última vez que aparece 'AAAA' con palabras de anexos junto a ellas, todas con \w+ (dos veces) y $ (una vez).

  2. Después del proceso de coincidencia de patrones, tendrá que usar el _sre.SRE_Match. group método de _sre.SRE_Match. group para obtener el valor de _sre.SRE_Match objeto _sre.SRE_Match y, por supuesto, obtener el grupo zeroth (primero), ya que sabemos que la search solo retiene una coincidencia (el zeroth).

Aquí está el regex101 de ello.

Aquí están los tiempos para todas las respuestas (excepto la respuesta de JGFMK, ya que es difícil):

 >>> 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 >>> 

Estoy probando todos los tiempos usando el módulo timeit , y también estoy haciendo el number=1000000 por lo que toma mucho más tiempo.