Python regex encontrar todas as correspondências sobrepostas?

98

Estou tentando encontrar cada série de 10 dígitos de números dentro de uma série maior de números usando re no Python 2.6.

Sou facilmente capaz de pegar nenhuma correspondência sobreposta, mas quero todas as correspondências da série numérica. Por exemplo.

em "123456789123456789"

Devo obter a seguinte lista:

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

Encontrei referências a um "lookahead", mas os exemplos que vi mostram apenas pares de números, em vez de agrupamentos maiores e não fui capaz de convertê-los além dos dois dígitos.

calças de ganga
fonte
6
As soluções apresentadas não funcionarão quando as correspondências sobrepostas começarem no mesmo ponto, por exemplo, comparar "a | ab | abc" com "abcd" retornará apenas um resultado. Existe uma solução para isso que não envolva chamar match () várias vezes, mantendo o controle manual do limite 'final'?
Vítor De Araújo
@ VítorDeAraújo: regexes sobrepostas como (a|ab|abc)podem geralmente ser reescritas como não sobrepostas com grupos de captura aninhados, por exemplo (a(b(c)?)?)?, onde ignoramos todos, exceto o grupo de captura mais externo (ou seja, mais à esquerda) ao desempacotar uma correspondência; reconhecidamente isso é um pouco doloroso e menos legível. Essa também será uma regex de melhor desempenho para corresponder.
smci de

Respostas:

175

Use um grupo de captura dentro de um lookahead. O lookahead captura o texto no qual você está interessado, mas a correspondência real é tecnicamente a substring de largura zero antes do lookahead, então as correspondências não são tecnicamente sobrepostas:

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]
carne_mecânica
fonte
2
Minha resposta é pelo menos 2 vezes mais rápida do que esta. Mas essa solução é complicada, eu voto positivamente.
eyquem
16
Explicação = em vez de procurar o padrão (10 dígitos), ele procura por qualquer coisa SEGUIDO pelo padrão. Assim, ele encontra a posição 0 da string, a posição 1 da string e assim por diante. Em seguida, ele pega o grupo (1) - o padrão correspondente e faz uma lista deles. Muito legal.
Tal Weiss
Eu não tinha ideia de que você poderia usar grupos correspondentes dentro de lookaheads, que normalmente não deveriam ser incluídos em uma correspondência (e os subgrupos correspondidos de fato não aparecem a correspondência completa). Como essa técnica ainda parece funcionar no Python 3.4, acho que é considerada um recurso, e não um bug.
JAB
10
Entrei no StackOverflow, respondi a perguntas e aumentei minha reputação apenas para poder votar a favor dessa resposta. Estou preso ao Python 2.4 por enquanto, então não posso usar as funções regex mais avançadas do Python 3, e esse é exatamente o tipo de truque bizarro que eu estava procurando.
TheSoundDefense
2
Você poderia adicionar mais explicação ao código. Não é a melhor maneira de acordo com Stack Overflow, apenas ter o código em uma resposta. Isso definitivamente ajudará as pessoas.
Akshay Hazari de
77

Você também pode tentar usar o módulo de terceirosregex (não re), que oferece suporte a correspondências sobrepostas.

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

Gosto de regexes, mas elas não são necessárias aqui.

Simplesmente

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
eyquem
fonte
10
Regexes só não são necessários aqui porque você está aplicando o conhecimento especial "dentro de uma série maior de números", então você já sabe que cada posição 0 <= i < len(s)-n+1é garantidamente o início de uma correspondência de 10 dígitos. Também acho que seu código poderia ser acelerado, seria interessante codificar para velocidade.
smci de