Pregunta

Digamos que tengo lo siguiente:

  

{{Hola | Hola | Hola} {mundo | tierra} |   {Adiós | despedida} {noobs | n3wbz | n00blets}}

Y quiero que eso se convierta en cualquiera de los siguientes:

Hello world 
Goodbye noobs 
Hi earth
farewell n3wbz 
// etc.

Prestar atención a la forma en que " girando " La sintaxis está anidada. Podría anidarse mil millones de capas de profundidad por todo lo que sabemos.

Puedo hacer esto fácilmente, excepto que una vez que están anidados como el ejemplo anterior, mi expresión regular falla y los resultados no son correctos.

¿Podría alguien mostrar un ejemplo en un lenguaje .NET o en Python?

¿Fue útil?

Solución

Una manera simple con re.subn , que también puede aceptar una función en lugar de una cadena de reemplazo:

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()

Simplemente reemplaza todas las opciones más profundas que encuentra, luego itera hasta que no quede ninguna opción. subn devuelve una tupla con el resultado y cuántos reemplazos se hicieron, lo cual es conveniente para detectar el final del procesamiento.

Mi versión de select () puede ser reemplazada por la de Bobince que usa random.choice () y es más elegante si solo desea mantener un selector aleatorio. Si desea crear un árbol de opciones, puede extender la función anterior, pero necesitará variables globales para realizar un seguimiento de dónde se encuentra, por lo que mover las funciones a una clase tendría sentido. Esto es solo una pista, no desarrollaré esa idea ya que no era realmente la pregunta original.

Finalmente, tenga en cuenta que debe usar r.subn (select, s, re.U) si necesita cadenas Unicode ( s = u " {...} " )

Ejemplo:

>>> s = "{{Hello|Hi|Hey} {world|earth} | {Goodbye|farewell} {noobs|n3wbz|n00blets}}"
>>> print spinner(s)
'farewell n3wbz'

Editar: Se reemplazó sub por subn para evitar un bucle infinito (gracias a Bobince por señalarlo) y hacerlo más eficiente, y reemplazó {([^ {}] +)} por {([^ {}] *)} para extraer también llaves vacías. Eso debería hacerlo más robusto para los patrones mal formateados.

Para las personas a las que les gusta poner lo más posible en una línea (lo que personalmente no recomendaría):

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()

Otros consejos

Debería ser bastante simple, simplemente no permita que un juego de llaves incluya otro, luego llame repetidamente haciendo reemplazos desde las coincidencias internas hacia afuera:

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 inversor de expresiones regulares utiliza pyparsing para generar cadenas coincidentes (con algunas restricciones, no se permiten símbolos de repetición ilimitados como + y *). Si reemplaza {} 's con ()' s para convertir su cadena original en una expresión regular, el inversor genera esta lista:

Helloworld
Helloearth
Hiworld
Hiearth
Heyworld
Heyearth
Goodbyenoobs
Goodbyen3wbz
Goodbyen00blets
farewellnoobs
farewelln3wbz
farewelln00blets

(Sé que los espacios están colapsados, pero tal vez este código te dé algunas ideas sobre cómo atacar este problema).

Usaría re.finditer y construiría un árbol de análisis básico para determinar el nivel de anidación. Para hacerlo, usaría el atributo span del objeto de coincidencia de expresiones regulares:

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

Esto le daría la profundidad de anidamiento de las expresiones entre corchetes. Agregue una lógica similar para las tuberías y estará en camino de resolver el problema.

Recomiendo echar un vistazo a el motor dado para inspirarte.

He hecho una implementación de algo inspirado por esto en el esquema y el AST del esquema aprovechado para expresar mis necesidades.

Específicamente, recomiendo encarecidamente que no intentes usar una expresión regular como un analizador en general.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top