Как сделать регулярное выражение статьи спиннер? - PullRequest
2 голосов
/ 28 ноября 2009

Допустим, у меня есть следующее:

{{Hello | Hi | Hey} {world | earth} | {Goodbye | прощай} {noobs | n3wbz | n00blets}}

И я хочу, чтобы это превратилось в любое из следующего:

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

Обращая внимание на способ вложенного синтаксиса. Все, что мы знаем, может быть вложено в миллиард слоев.

Я могу сделать это легко, за исключением случаев, когда они вложены, как в приведенном выше примере, мое регулярное выражение не работает и результаты не верны.

Может кто-нибудь показать пример на языке .NET или Python, пожалуйста?

Ответы [ 5 ]

5 голосов
/ 28 ноября 2009

Простой способ с re.subn , который также может принимать функцию вместо строки замены:

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

Он просто заменяет все самые глубокие варианты, которые он встречает, а затем повторяется до тех пор, пока не останется выбора. subn возвращает кортеж с результатом и количеством выполненных замен, что удобно для определения окончания обработки.

Моя версия select() может быть заменена на версию Bobince, которая использует random.choice() и более элегантна, если вы просто хотите использовать случайный селектор. Если вы хотите построить дерево выбора, вы можете расширить вышеупомянутую функцию, но вам понадобятся глобальные переменные, чтобы отслеживать, где вы находитесь, поэтому перемещение функций в класс имело бы смысл. Это всего лишь подсказка, я не буду развивать эту идею, так как это не был оригинальный вопрос.

Обратите внимание, наконец, что вы должны использовать r.subn(select, s, re.U), если вам нужны строки Unicode (s = u"{...}")

Пример:

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

Редактировать: Заменить sub на subn, чтобы избежать бесконечного цикла (спасибо Bobince за это) и сделать его более эффективным, и заменить {([^{}]+)} на {([^{}]*)}, чтобы извлечь пустой фигурные скобки также. Это должно сделать его более устойчивым к плохо отформатированным шаблонам.

Для людей, которые любят ставить как можно больше на одну строку (что я лично не рекомендовал бы):

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()
4 голосов
/ 28 ноября 2009

Должно быть довольно просто, просто запретить набор скобок включать другой, а затем повторно вызывать выполнение замен из внутренних совпадений:

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'
2 голосов
/ 28 ноября 2009

Этот инвертор регулярных выражений использует pyparsing для генерации соответствующих строк (с некоторыми ограничениями - неограниченные символы повторения, такие как + и *, не допускаются). Если вы замените {} на (), чтобы превратить вашу исходную строку в регулярное выражение, инвертор сгенерирует этот список:

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

(Я знаю, что пробелы сокращены, но, возможно, этот код даст вам некоторые идеи о том, как решить эту проблему.)

1 голос
/ 28 ноября 2009

Я бы порекомендовал взглянуть на движок Дада для вдохновения.

Я реализовал что-то вдохновленное этим в схеме и использовало AST схемы, чтобы выразить свои потребности.

В частности, я бы настоятельно рекомендовал не пытаться использовать регулярные выражения в качестве синтаксического анализатора в целом.

1 голос
/ 28 ноября 2009

Я бы использовал re.finditer и построил бы базовое дерево разбора, чтобы определить уровень вложенности. Для этого я бы использовал атрибут span объекта соответствия регулярному выражению:

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

Это даст вам глубину вложенности выражений в скобках. Добавьте аналогичную логику для каналов, и вы окажетесь на пути к решению проблемы.

...