Cómo usar expresiones regulares para encontrar todas las coincidencias superpuestas

4 minutos de lectura

avatar de usuario
pantalones danspans

Estoy tratando de encontrar cada serie de números de 10 dígitos dentro de una serie más grande de números usando re en Python 2.6.

Puedo obtener fácilmente coincidencias que no se superponen, pero quiero todas las coincidencias en la serie de números. P.ej.

en “123456789123456789”

Debería obtener la siguiente lista:

[1234567891,2345678912,3456789123,4567891234,5678912345,6789123456,7891234567,8912345678,9123456789]

He encontrado referencias a una “búsqueda anticipada”, pero los ejemplos que he visto solo muestran pares de números en lugar de agrupaciones más grandes y no he podido convertirlos más allá de los dos dígitos.

  • Las soluciones presentadas no funcionarán cuando las coincidencias superpuestas comiencen en el mismo punto, por ejemplo, hacer coincidir “a|ab|abc” con “abcd” solo arrojará un resultado. ¿Existe una solución para eso que no implique llamar a match() varias veces, siguiendo manualmente el límite del ‘final’?

    – Vítor De Araújo

    28/10/2011 a las 19:10

  • @VítorDeAraújo: expresiones regulares superpuestas como (a|ab|abc) generalmente se pueden reescribir como no superpuestos con grupos de captura anidados, por ejemplo (a(b(c)?)?)?, donde ignoramos todo menos el grupo de captura más externo (es decir, más a la izquierda) al desempaquetar una coincidencia; es cierto que esto es un poco doloroso y menos legible. Esta también será una expresión regular más eficaz para igualar.

    – smci

    20 de noviembre de 2017 a las 2:30

avatar de usuario
carne_mecanica

Use un grupo de captura dentro de una búsqueda anticipada. La búsqueda anticipada captura el texto que le interesa, pero la coincidencia real es técnicamente la subcadena de ancho cero antes de la búsqueda anticipada, por lo que las coincidencias técnicamente no se superponen:

import re 
s = "123456789123456789"
matches = re.finditer(r'(?=(\d{10}))',s)
results = [int(match.group(1)) for match in matches]
# results: 
# [1234567891,
#  2345678912,
#  3456789123,
#  4567891234,
#  5678912345,
#  6789123456,
#  7891234567,
#  8912345678,
#  9123456789]

  • Mi respuesta es al menos 2 veces más rápida que esta. Pero esta solución es complicada, la voto a favor.

    – eyquem

    5 de julio de 2013 a las 10:33

  • Explicación = en lugar de buscar el patrón (10 dígitos), busca cualquier cosa SEGUIDA POR el patrón. Entonces encuentra la posición 0 de la cadena, la posición 1 de la cadena y así sucesivamente. Luego toma el grupo (1) – el patrón coincidente y hace una lista de ellos. Muy genial.

    – Tal Weiss

    18 de julio de 2013 a las 20:28

  • No tenía idea de que podría usar grupos coincidentes dentro de las búsquedas anticipadas, que normalmente no se supone que se incluyan en una coincidencia (y los subgrupos coincidentes, de hecho, no aparecen en la coincidencia completa). Como esta técnica todavía parece funcionar en Python 3.4, supongo que se considera una característica más que un error.

    – JAB

    27/03/2014 a las 18:35

  • Me uní a StackOverflow, respondí preguntas y mejoré mi reputación solo para poder votar esta respuesta. Estoy atascado con Python 2.4 por ahora, así que no puedo usar las funciones de expresiones regulares más avanzadas de Python 3, y este es justo el tipo de truco extraño que estaba buscando.

    – La defensa del sonido

    7 julio 2014 a las 17:17

  • ¿Podría agregar más explicaciones al código? No es la mejor manera según Stack Overflow, solo tener código en una respuesta. Definitivamente ayudará a la gente.

    – Akshay Hazari

    17 de septiembre de 2017 a las 8:12

avatar de usuario
david c

También puedes intentar usar el tercero regex módulo (no re), que admite coincidencias superpuestas.

>>> import regex as re
>>> s = "123456789123456789"
>>> matches = re.findall(r'\d{10}', s, overlapped=True)
>>> for match in matches: print(match)  # print match
...
1234567891
2345678912
3456789123
4567891234
5678912345
6789123456
7891234567
8912345678
9123456789

  • Yo obtengo: TypeError: findall() got an unexpected keyword argument 'overlapped'

    – Carsten

    17 oct 2020 a las 19:34

  • @Carsten: primero debe instalar el regex módulo: pip install regex

    – David C.

    19 de octubre de 2020 a las 1:38

  • Eso funcionó, gracias. Hubiera pensado que obtendría un error de importación si la expresión regular no está instalada

    – Carsten

    19 oct 2020 a las 6:55

Me gustan las expresiones regulares, pero no son necesarias aquí.

Simplemente

s =  "123456789123456789"

n = 10
li = [ s[i:i+n] for i in xrange(len(s)-n+1) ]
print '\n'.join(li)

resultado

1234567891
2345678912
3456789123
4567891234
5678912345
6789123456
7891234567
8912345678
9123456789

  • Las expresiones regulares no son necesarias aquí porque está aplicando el conocimiento especial “dentro de una serie más grande de números”, por lo que ya conoce cada posición 0 <= i < len(s)-n+1 se garantiza que será el comienzo de una coincidencia de 10 dígitos. También me imagino que su código podría acelerarse, sería interesante codificar golf para obtener velocidad.

    – smci

    20 de noviembre de 2017 a las 2:34


Aprovechando la respuesta aceptada, lo siguiente también funciona actualmente

import re
s = "123456789123456789"
matches = re.findall(r'(?=(\d{10}))',s)
results = [int(match) for match in matches]

manera convencional:

import re


S = '123456789123456789'
result = []
while len(S):
    m = re.search(r'\d{10}', S)
    if m:
        result.append(int(m.group()))
        S = S[m.start() + 1:]
    else:
        break
print(result)

¿Ha sido útil esta solución?