Come rendere regex spinner articolo?
Domanda
Diciamo che ho il seguente:
{{Hello | Hi | Hey} {world | earth} | {Arrivederci | addio} {noobs | n3wbz | n00blets}}
E voglio che si trasformi in uno dei seguenti:
Hello world
Goodbye noobs
Hi earth
farewell n3wbz
// etc.
Prestando attenzione al modo in cui " gira " la sintassi è nidificata. Potrebbe essere nidificato a un miliardo di strati di profondità per quanto ne sappiamo.
Posso farlo facilmente, tranne quando sono nidificati come nell'esempio sopra il mio regex si rovina e i risultati non sono corretti.
Qualcuno potrebbe mostrare un esempio in un linguaggio .NET o Python per favore?
Soluzione
Un modo semplice con re.subn , che può anche accettare una funzione anziché una stringa di sostituzione:
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()
Sostituisce semplicemente tutte le scelte più profonde che incontra, quindi scorre fino a quando non rimane alcuna scelta. subn
restituisce una tupla con il risultato e il numero di sostituzioni effettuate, il che è comodo per rilevare la fine dell'elaborazione.
La mia versione di select ()
può essere sostituita da quella di Bobince che usa random.choice ()
ed è più elegante se vuoi solo attenerti a un selettore casuale. Se vuoi costruire un albero di scelta, potresti estendere la funzione sopra, ma avrai bisogno di variabili globali per tenere traccia di dove ti trovi, quindi avrebbe senso spostare le funzioni in una classe. Questo è solo un suggerimento, non svilupperò quell'idea poiché non era proprio la domanda originale.
Nota infine che dovresti usare r.subn (select, s, re.U)
se hai bisogno di stringhe unicode ( s = u " {...} "
)
Esempio:
>>> s = "{{Hello|Hi|Hey} {world|earth} | {Goodbye|farewell} {noobs|n3wbz|n00blets}}"
>>> print spinner(s)
'farewell n3wbz'
Modifica: Sostituito sub
con subn
per evitare un ciclo infinito (grazie a Bobince per segnalarlo) e renderlo più efficiente, e sostituito {([^ {}] +)}
con {([^ {}] *)}
per estrarre anche parentesi graffe vuote. Ciò dovrebbe rendere più robusti i pattern mal formattati.
Per le persone a cui piace mettere il più possibile su una linea (che personalmente non incoraggerei):
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()
Altri suggerimenti
Dovrebbe essere abbastanza semplice, basta impedire a un set di controventi di includerne un altro, quindi chiamare ripetutamente facendo sostituzioni dalle partite interne verso l'esterno:
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'
Questo regex inverter utilizza pyparsing per generare stringhe corrispondenti (con alcune restrizioni - non sono ammessi simboli di ripetizione illimitati come + e *). Se sostituisci {} con () per trasformare la tua stringa originale in regex, l'inverter genera questo elenco:
Helloworld
Helloearth
Hiworld
Hiearth
Heyworld
Heyearth
Goodbyenoobs
Goodbyen3wbz
Goodbyen00blets
farewellnoobs
farewelln3wbz
farewelln00blets
(So che gli spazi sono crollati, ma forse questo codice ti darà alcune idee su come attaccare questo problema.)
Vorrei usare re.finditer e costruire un albero di analisi di base per determinare il livello di annidamento. Per farlo, vorrei usare l'attributo span dell'oggetto match 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
Questo ti darebbe la profondità di annidamento delle espressioni tra parentesi. Aggiungi una logica simile per le pipe e saresti sulla buona strada per risolvere il problema.
Consiglio di dare un'occhiata a il motore dada per ispirazione.
Ho fatto un'implementazione di qualcosa ispirato da questo nello schema e ho sfruttato l'AST dello schema per esprimere i miei bisogni.
In particolare, sconsiglio vivamente di provare a usare una regex come parser in generale.