Como fazer spinner artigo regex?
Pergunta
Digamos que eu tenho teh seguinte:
{{Olá | Hi | Hey} {mundo | terra} | {Adeus | despedida} {noobs | n3wbz | n00blets}}
E eu quero isso para se transformar em qualquer um dos seguintes:
Hello world
Goodbye noobs
Hi earth
farewell n3wbz
// etc.
Prestando atenção para a forma como a sintaxe "fiação" está aninhado. Pode ser aninhados um bilhão de camadas profundas para todos nós sabemos.
Eu posso fazer isso fácil, exceto uma vez que eles estão aninhados como no exemplo acima meus messes regex-se e os resultados não estão corretas.
Alguém poderia mostrar um exemplo em qualquer uma linguagem .NET ou Python por favor?
Solução
Uma maneira simples com re.subn , que também pode aceitar uma função em vez de uma cadeia de substituição:
import re
from random import randint
def select(m):
choices = m.group(1).split('|')
return choices[randint(0, len(choices)-1)]
def spinner(s):
r = re.compile('{([^{}]*)}')
while True:
s, n = r.subn(select, s)
if n == 0: break
return s.strip()
Ele simplesmente substitui todas as escolhas mais profundas que se encontra, em seguida, repete até que não haja restos de escolha. subn
retorna uma tupla com o resultado e quantas substituições foram feitas, o que é conveniente para detectar o fim do processamento.
A minha versão do select()
pode ser substituída pela usos random.choice()
de bobince e é mais elegante, se você só quer ficar com um seletor aleatório. Se você quer construir uma árvore de escolha, você pode estender a função acima, mas você vai precisar variáveis ??globais para manter o controle de onde você é, assim movendo as funções em uma classe faria sentido. Esta é apenas uma sugestão, não vou desenvolver essa idéia, pois não era realmente a questão orginial.
Note finalmente que você deve usar r.subn(select, s, re.U)
se precisar strings unicode (s = u"{...}"
)
Exemplo:
>>> s = "{{Hello|Hi|Hey} {world|earth} | {Goodbye|farewell} {noobs|n3wbz|n00blets}}"
>>> print spinner(s)
'farewell n3wbz'
Editar: sub
Substituído por subn
para evitar loop infinito (graças a bobince de indicá-lo) e torná-lo mais eficiente, e {([^{}]+)}
substituído por {([^{}]*)}
para extrair chaves vazias também. Que deve torná-lo mais robusto para padrões mal-formatadas.
Para as pessoas que gostam de colocar o máximo possível em uma linha (que eu, pessoalmente, não iria encorajar):
def spin(s):
while True:
s, n = re.subn('{([^{}]*)}',
lambda m: random.choice(m.group(1).split("|")),
s)
if n == 0: break
return s.strip()
Outras dicas
deve ser bastante simples, basta não permitir um conjunto cinta de incluir o outro, em seguida, chamar repetidamente fazendo substituições dos jogos internos para fora:
def replacebrace(match):
return random.choice(match.group(1).split('|'))
def randomizebraces(s):
while True:
s1= re.sub(r'\{([^{}]*)\}', replacebrace, s)
if s1==s:
return s
s= s1
>>> randomizebraces('{{Hello|Hi|Hey} {world|earth}|{Goodbye|farewell} {noobs|n3wbz|n00blets}}')
'Hey world'
>>> randomizebraces('{{Hello|Hi|Hey} {world|earth}|{Goodbye|farewell} {noobs|n3wbz|n00blets}}')
'Goodbye noobs'
Este regex inversor usos pyparsing para gerar seqüências de correspondência (com algumas restrições - símbolos de repetição ilimitada como + e * não são permitidos). Se você substituir {} é com () é para tornar a sua seqüência original em um regex, o inversor gera esta lista:
Helloworld
Helloearth
Hiworld
Hiearth
Heyworld
Heyearth
Goodbyenoobs
Goodbyen3wbz
Goodbyen00blets
farewellnoobs
farewelln3wbz
farewelln00blets
(Eu sei que os espaços estão fechados, mas talvez este código vai lhe dar algumas idéias sobre como atacar este problema.)
Gostaria de usar re.finditer e construir uma árvore básica de análise para determinar o nível de aninhamento. Para fazê-lo, gostaria de usar o atributo de extensão do objeto jogo regex:
text = '{{Hello|Hi|Hey} {world|earth} | {Goodbye|farewell} {noobs|n3wbz|n00blets}}'
import re
re_bracks = re.compile(r'{.+?}')
# subclass list for a basic tree datatype
class bracks(list):
def __init__(self, m):
self.m = m
# icky procedure to create the parse tree
# I hate these but don't know how else to do it
parse_tree = []
for m in re_bracks.finditer(text):
if not this_element:
# this first match
parse_tree.extend(element(m))
else:
# ... and all the rest
this_element = bracks(m)
this_start, this_end = m.span()
# if this match is nested in the old one ...
if this_start < previous_start and this_end > previous_end:
# nest it inside the previous one
previous_element.extend(this_element)
else:
# otherwise make it a child of the parse_tree
parse_tree.extend(element(m))
previous_element = this_element
previous_start, previous_end = this_start, this_end
Isto lhe daria a profundidade de aninhamento das expressões entre colchetes. Adicione um pouco de lógica semelhante para os tubos e você estará bem em seu caminho para resolver o problema.