2016-01-06 14 views
5

Sto provando a produrre varianti di stringa applicando le sostituzioni facoltativamente.Prodotto combinatorio delle sostituzioni regex

Ad esempio, uno schema di sostituzione sta rimuovendo qualsiasi sequenza di caratteri vuoti. Anziché sostituendo tutte le occorrenze come

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

- ho bisogno, invece, due varianti da elaborare per ciascuna volta, dal fatto che la sostituzione viene effettuata in una variante, ma non nell'altro. Per la stringa 'a b c' voglio avere le varianti

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

es. il prodotto incrociato di tutte le decisioni binarie (il risultato include ovviamente la stringa originale).

Per questo caso, le varianti possono essere prodotti usando re.finditer e 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 

Questo produce l'uscita desiderata per l'esempio precedente:

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

Tuttavia, questa soluzione funziona solo per fissa -string sostituzioni. Funzioni avanzate da re.sub come riferimenti di gruppo non può essere fatto in quel modo, come nel seguente esempio per l'inserimento di uno spazio dopo una sequenza di cifre all'interno di una parola:

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

Come può l'approccio essere esteso o modificato accettare qualsiasi argomento valido su re.sub (senza scrivere un parser per interpretare i riferimenti di gruppo)?

risposta

1

Pensando di fare una subst richiamabile che ottiene l'accesso per abbinare i dati, infine, mi ha fatto imparare su MatchObject.expand. Così, come approssimazione, con subst rimanendo una stringa 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 

Non sono sicuro, però, che questo copre tutta la flessibilità necessaria in riferimento alla stringa oggetto, essendo Bount al corrispondente partita. Mi è venuto in mente un power set indicizzato della stringa divisa, ma immagino che non sia lontano dal parser menzionato.

+1

Grazie, questo è un suggerimento molto buono! Le limitazioni per un argomento callable possono essere eliminate facilmente facendone il caso generale: nel ciclo interno, sostituire '... + match.expand (subst)' con '... + subst (match)'. Se l'argomento non è un callable per cominciare, basta avvolgere una funzione (all'inizio del codice): 'se non callable (subst): static_subst = subst; subst = lambda m: m.expand (static_subst) ' – lenz

1

ne dite di questo:

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 

Per ogni partita, si sia lasciato re.sub() fare il suo lavoro (con un numero da 1 a fermarsi dopo una sostituzione), o strappare via la parte invariato della stringa.

provarlo con i tuoi esempi come questo

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

ottengo

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

Grazie per il suggerimento. Il problema con il clipping della stringa di destinazione prima di applicare 're.sub' è che rimuove il contesto necessario in alcuni casi d'angolo. Ad esempio, il pattern 'r '\ B (\ d + | [A-Z] +) \ B'' corrisponderà a' A 'nella stringa'' abc1Adef'', ma non nella versione troncata ''Adef''.So che qui sto selezionando casi rari, ma questo è esattamente il tipo di situazioni che producono bug difficili da rintracciare. – lenz

+0

Sì, hai ragione - questo approccio era troppo ingenuo per coprire tutti i casi d'angolo sgradevoli ... – ThorngardSO

Problemi correlati