Possível recuperar um conjunto arbitrário de grupos nomeados em uma merda com o módulo RE do Python?
Pergunta
Isso é super útil para alguns problemas:
>>> re.search('(?P<b>.b.).*(?P<i>.i.)', 'abcdefghijk').groupdict()
{'i': 'hij', 'b': 'abc'}
Mas e se eu não souber que ordem esperar antes do tempo?
atualizar
Por exemplo, digamos que eu tenho uma variável de entrada contendo alguma ordem desconhecida de caracteres e acontece que 'B' vem depois de 'i'. Eu ainda quero poder fazer referência aos grupos para '.b'. e eu.' sem ter que pedir meu regex de acordo com a ordem deles na entrada var. Então, eu gostaria de poder fazer algo assim, mas não sei se é possível:
>>> re.search('(?P<b>.b.)|(?P<i>.i.)', unknown_order_alphabet_str).groupdict()
{'i': 'hij', 'b': 'abc'}
Atualização final
Eu procurei e acalmei um monte de meu cérebro, mas não posso gerar bons leads. Descobrindo que essa funcionalidade não existiria, porque provavelmente a única maneira de fazer isso é escanear a corda inteira uma vez para cada grupo (o que, é claro, eu poderia fazer em um loop), mas pensei em ver o que o cérebro do Stackoverflow Tive que dizer sobre isso.
Obrigado pela ajuda,
Josh
Solução
Use uma barra vertical ("ou") no padrão de re e finditer
Para obter todos os objetos de interesse correspondentes: cada um terá um groupdict
com None
Como o valor para os grupos não envolvidos nessa correspondência, e você pode "mesclar" os ditos como preferir.
Por exemplo:
import re
def mergedgroupdict(pattern, thestring):
there = re.compile(pattern)
result = {}
for mo in there.finditer(thestring):
d = mo.groupdict()
for k in d:
if k not in result and d[k] is not None:
result[k] = d[k]
return result
Isso usa uma estratégia de mesclagem que é apenas para escolher a primeira correspondência real para cada grupo nomeado no padrão. Agora, por exemplo
>>> mergedgroupdict('(?P<b>.b.)|(?P<i>.i.)', 'abcdefghijk')
{'i': 'hij', 'b': 'abc'}
>>> mergedgroupdict('(?P<b>.b.)|(?P<i>.i.)', 'abcdefghijk'[::-1])
{'i': 'jih', 'b': 'cba'}
Presumivelmente, como você deseja, se eu interpretar sua pergunta corretamente.
Outras dicas
>>> [m.groupdict() for m in re.finditer('(?P<b>.b.)|(?P<i>.i.)', 'abcdefghijk')]
[{'i': None, 'b': 'abc'}, {'i': 'hij', 'b': None}]
Parece funcionar bem, embora se você tenha muitos grupos verificando qual não None
Pode ser entediante.
Isso encontra tudo .b.
e tudo .i.
combina na string. Se você quisesse ter certeza de que encontrou um de cada um, terá que verificar isso manualmente também.
O mais próximo que posso conseguir é o seguinte:
>>> [match.groupdict() for match in re.finditer('(?P<b>.b.)|(?P<i>.i.)', 'abcdefghijk')]
[{'i': None, 'b': 'abc'}, {'i': 'hij', 'b': None}]
Como você combina os dicionários depende se você está esperando mais de uma partida. Se você quiser apenas uma correspondência cada uma, você pode fazer:
>>> results = {}
>>> for match in re.finditer('(?P<b>.b.)|(?P<i>.i.)', 'abcdefghijk'):
... results.update(dict((k,v) for k, v in match.groupdict().iteritems() if v is not None))
...
>>> results
{'i': 'hij', 'b': 'abc'}
Ou para várias correspondências:
>>> results = defaultdict(lambda: [])
>>> for match in re.finditer('(?P<b>.b.)|(?P<i>.i.)', 'abcdefghijkabcdefghijk'):
... for k, v in match.groupdict().iteritems():
... if v is not None:
... results[k].append(v)
...
>>> results
defaultdict(<function <lambda> at 0x7f53d0992c08>, {'i': ['hij', 'hij'], 'b': ['abc', 'abc']})
Aqui está uma maneira que não requer finditer
nem fusão de dicionário:
>>> pat = re.compile(r'(?:.*?(?:(?P<b>.b.)|(?P<i>.i.))){2}')
>>> pat.search('abcdefghijk').groupdict()
{'i': 'hij', 'b': 'abc'}
>>> pat.search('aicdefghbjk').groupdict()
{'i': 'aic', 'b': 'hbj'}
Isso está assumindo cada um dos personagens b
e i
aparece exatamente uma vez na sua string, caso contrário:
- Se um dos personagens pode estar faltando, você pode usar
{,2}
ao invés de{2}
. - Se um dos personagens aparecer mais de uma vez, a pesquisa recuperará as duas primeiras aparições de qualquer deles (por exemplo, pode encontrar
b
duas vezes e não encontrari
de forma alguma).
Aqui está um canto tardio para o jogo em um toque, que também é legível para iniciantes:
>>> dict([(name, re.search(pattern, "abcdefghijk").group())
for name, pattern in {"b": ".b.", "i": ".i"}.items()])
{'b': 'abc', 'i': 'hij'}