2016-01-06 22 views
5

Próbuję tworzyć warianty ciągów, stosując opcjonalnie substytucje.Kombinatoryczny produkt substytucji regex

Na przykład, jednym schematem zastępowania jest usunięcie dowolnej sekwencji pustych znaków. Zamiast zastąpienie wszystkich zjawisk jak

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

- muszę, natomiast dwa warianty być wytwarzane na każdym wystąpieniu, że podstawienie prowadzi się w jednym z wariantów, ale nie w drugiej. dla struny 'a b c' chcę mieć warianty

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

tj. krzyżowy produkt wszystkich decyzji binarnych (wynik oczywiście zawiera oryginalny ciąg znaków).

W tym przypadku, warianty mogą być wytwarzane re.finditer i 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 

To daje pożądany wynik dla powyższego przykładu:

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

Jednakże rozwiązanie to działa tylko na stałe -string zastępstwa. zaawansowane funkcje z re.sub jak odniesień grupy nie można zrobić tak, jak w poniższym przykładzie do wstawiania spacji po sekwencji cyfr wewnątrz słowem:

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

jaki sposób podejście zostać rozszerzony lub zmieniony zaakceptować dowolny poprawny argument do re. (bez zapisywania analizatora składni do interpretowania odniesień do grupy)?

Odpowiedz

1

Myśląc o co subst wywoływalnym który uzyskuje dostęp pasujące dane wreszcie mnie nauczyć o MatchObject.expand. Tak więc, jako przybliżenie, z subst pobytu jest r ciąg,

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 

Nie jestem pewien jednak, że ten obejmuje całą potrzebną elastyczność odnosząc się do napisu, będąc Bount do analogicznego meczu. Pojawił się indeksowany zestaw mocy podzielonego ciągu, ale myślę, że to nie jest daleko od wspomnianego parsera.

+1

Dzięki, to bardzo dobra wskazówka! Ograniczenia dla argumentu, który można wywołać, można łatwo usunąć, nadając mu ogólny charakter: w pętli wewnętrznej zamień '... + match.expand (subst)' na '... + subst (dopasowanie)'. Jeśli argument nie jest uruchamiany, po prostu zapisz funkcję (na początku kodu): 'if not callla (subst): static_subst = subst; subst = lambda m: m.expand (static_subst) ' – lenz

1

Jak o tym:

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 

dla każdego meczu, to albo niech re.sub() wykonywać swoje zadania (o liczbie od 1 do zatrzymania po jednej substytucji), albo wyrwać część niezmieniona ciągu.

próbuje ją ze swoimi przykładów jak ten

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

uzyskać

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

Dzięki za sugestię. Problem z obcinaniem łańcucha docelowego przed zastosowaniem 're.sub' polega na tym, że usuwa niezbędny kontekst w niektórych przypadkach narożnych. Na przykład wzorzec 'r '\ B (\ d + | [A-Z] +) \ B'' będzie pasować do litery" A "w ciągu' 'abc1Adef'', ale nie do przycinanej wersji'' Adef''.Wiem, że wybieram tu rzadkie przypadki, ale właśnie w takich sytuacjach powstają trudne do wykrycia błędy. – lenz

+0

Tak, masz rację - to podejście było zbyt naiwne, by objąć wszystkie nieprzyjemne przypadki narożników ... – ThorngardSO

Powiązane problemy