2016-01-06 3 views
5

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

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

>>> re.sub(r'\s+', '', 'a b c') 
'abc' 

- мне нужно, вместо этого, два варианта должны быть произведены для каждого случая, в том, что замена выполняется в одном варианте, но не в другом. Для строки 'a b c' Я хочу иметь варианты

['a b c', 'a bc', 'ab c', 'abc'] 

т.е.. перекрестное произведение всех двоичных решений (результат, очевидно, включает исходную строку).

Для этого случая, варианты могут быть получены с использованием re.finditer и itertools.product:

def vary(target, pattern, subst): 
    occurrences = [m.span() for m in pattern.finditer(target)] 
    for path in itertools.product((True, False), repeat=len(occurrences)): 
     variant = '' 
     anchor = 0 
     for (start, end), apply_this in zip(occurrences, path): 
      if apply_this: 
       variant += target[anchor:start] + subst 
       anchor = end 
     variant += target[anchor:] 
     yield variant 

Это дает желаемый результат для приведенного выше примера:

>>> list(vary('a b c', re.compile(r'\s+'), '')) 
['abc', 'ab c', 'a bc', 'a b c'] 

Однако это решение работает только для фиксированной -строчные замены. Расширенные функции из re.sub как ссылки группы не могут быть сделаны так, , как в следующем примере для вставки пробела после последовательности цифр в слове:

re.sub(r'\B(\d+)\B'), r'\1 ', 'abc123def') 

Как этот подход может быть расширен или изменен принять любой действительный аргумент для re.sub (без написания анализатора для интерпретации ссылок на группы)?

ответ

1

Думая о создании subst отзывной, который получает доступ к сопрягать данные, наконец, заставил меня узнать о MatchObject.expand. Так, как приближение, с subst пребывания в r строку,

def vary(target, pattern, subst): 
    matches = [m for m in pattern.finditer(target)] 
    occurrences = [m.span() for m in matches] 
    for path in itertools.product((True, False), repeat=len(occurrences)): 
     variant = '' 
     anchor = 0 
     for match, (start, end), apply_this in zip(matches, occurrences, path): 
      if apply_this: 
       variant += target[anchor:start] + match.expand(subst) 
       anchor = end 
     variant += target[anchor:] 
     yield variant 

Я не уверен, однако, что это охватывает всю необходимую гибкость в виду строки темы, будучи bount к соответствующему матча. Напомнил индексный набор мощности сплит-струны, но я думаю, что это недалеко от упомянутого парсера.

+1

Спасибо, это очень хороший намек! Ограничения для вызываемого аргумента можно легко удалить, сделав его общим: во внутреннем цикле замените '... + match.expand (subst)' на '... + subst (match)'. Если аргумент не является вызываемым для начала, просто оберните функцию (в начале кода): 'if not callable (subst): static_subst = subst; subst = lambda m: m.expand (static_subst) ' – lenz

1

Как об этом:

def vary(target, pattern, subst): 
    numOccurences = len (pattern.findall (target)) 

    for path in itertools.product((True, False), repeat=numOccurences): 

    variant  = '' 
    remainingStr = target 

    for currentFlag in path: 

     if currentFlag: 
     remainingStr = pattern.sub (subst, remainingStr, 1) 
     else: 
     currentMatch = pattern.search (remainingStr); 
     variant += remainingStr[:currentMatch.end()] 
     remainingStr = remainingStr[currentMatch.end():] 

    variant += remainingStr 

    yield variant 

Для каждого матча, мы либо пусть re.sub() делать свою работу (с отсчетом от 1 до остановки после одной замены), или мы расхватываем неизменную часть строки.

Попытка его с примерами, как этот

target = 'a b c' 
pattern = re.compile(r'\s+') 
subst = '' 

print list (vary(target, pattern, subst)) 

target = 'abc123def' 
pattern = re.compile(r'\B(\d+)\B') 
subst = r'\1 ' 

print list (vary(target, pattern, subst)) 

Я получаю

['abc', 'ab c', 'a bc', 'a b c'] 
['abc123 def', 'abc123def'] 
+0

Спасибо за ваше предложение. Проблема с отсечением целевой строки перед применением 're.sub' заключается в том, что она удаляет необходимый контекст в некоторых случаях. Например, шаблон 'r '\ B (\ d + | [A-Z] +) \ B'' будет соответствовать« A »в строке' 'abc1Adef'', но не в обрезанной версии' 'Adef''.Я знаю, что я выбираю редкие случаи здесь, но это именно такие ситуации, которые вызывают жесткие ошибки. – lenz

+0

Да, вы правы - этот подход был слишком наивным, чтобы покрыть все неприятные угловые шкафы ... – ThorngardSO

Смежные вопросы