2012-07-11 11 views
7

Sto tentando di eliminare alcune cose da un blocco di testo utilizzando espressioni regolari. Ho tutti i miei schemi pronti, ma non riesco a essere in grado di rimuovere due (o più) che si sovrappongono.Combinazione di sostituzioni multiple di espressioni regolari

Ad esempio:

import re 

r1 = r'I am' 
r2 = r'am foo' 

text = 'I am foo' 

re.sub(r1, '', text) # Returns ' foo' 
re.sub(r2, '', text) # Returns 'I ' 

Come faccio a sostituire entrambe le occorrenze contemporaneamente e finiscono con una stringa vuota?


ho finito per usare una versione leggermente modificata di Ned Batchelder's answer:

def clean(self, text): 
    mask = bytearray(len(text)) 

    for pattern in patterns: 
    for match in re.finditer(pattern, text): 
     r = range(match.start(), match.end()) 

     mask[r] = 'x' * len(r) 

    return ''.join(character for character, bit in zip(text, mask) if not bit) 

risposta

12

Non è possibile farlo con chiamate consecutive re.sub come si è mostrato. È possibile utilizzare re.finditer per trovarli tutti. Ogni partita ti fornirà un oggetto match, che ha gli attributi .start e .end che indicano le loro posizioni. È possibile raccogliere tutti quelli insieme e quindi rimuovere i caratteri alla fine.

Qui utilizzo uno bytearray come una stringa mutabile, utilizzata come maschera. È inizializzato a zero byte e contrassegno con una 'x' tutti i byte che corrispondono a qualsiasi regex. Poi io uso la maschera di bit per selezionare i caratteri per mantenere nella stringa originale, e costruire una nuova stringa con solo i caratteri senza eguali:

bits = bytearray(len(text)) 
for pat in patterns: 
    for m in re.finditer(pat, text): 
     bits[m.start():m.end()] = 'x' * (m.end()-m.start()) 
new_string = ''.join(c for c,bit in zip(text, bits) if not bit) 
+0

Non ho mai pensato agli attributi 'start' e' end' per gli oggetti match. Sono più che sicuro che funzionerà, quindi grazie! – Blender

+1

Ottima risposta! Ho aggiunto '()' a 'start' e' end', perché questi sono metodi, non attributi. – georg

+0

@ thg435: grazie, avrei dovuto provarlo! :) –

2

di non essere un handicap, ma la risposta breve è che sono abbastanza sicuro non si può. È possibile modificare l'espressione regolare in modo che non richieda la sovrapposizione?

Se si desidera eseguire questa operazione, tentare di tenere traccia dello avviare e arrestare gli indici di ogni corrispondenza effettuata sulla stringa originale. Quindi passare attraverso la stringa e mantenere solo i caratteri non in qualsiasi intervallo di eliminazione?

1

abbastanza efficiente troppo è una soluzione provenienti da ... Perl combinare le espressioni regolari in uno:

# aptitude install regexp-assemble 
$ regexp-assemble 
I am 
I am foo 
Ctrl + D 
I am(?: foo)? 

regexp-assemblare prende tutte le varianti di espressioni regolari o stringa che si desidera abbinare e poi combinarli in uno. E sì cambia il problema iniziale ad un altro dato che non si tratta di corrispondenza regexp sovrappongono più, ma che unisce regexp per una partita

e quindi è possibile utilizzarlo nel codice:

$ python 
Python 2.7.3 (default, Aug 1 2012, 05:14:39) 
[GCC 4.6.3] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> import re 
>>> re.sub("I am foo","I am(?: foo)?","") 
'' 

Un porto di Regexp :: Assemblare in python sarebbe bello :)

+0

Il comando è 'aptitude install libregexp-assemble-perl'. Non sono riuscito a trovare rapidamente alcuna traccia di un pacchetto precedente con il nome che hai indicato, ma forse sei su una distro diversa; questo è per Debian stable. – tripleee

+0

Inoltre, nelle versioni precedenti del pacchetto, la demo era solo in '/ usr/share/doc/libregexp-assemble-perl/examples/assemble.gz' - Volevo questo in una casella' squeeze', dove lo script non è installato con il nome che si indica. – tripleee

1

Ecco un'alternativa che filtra le stringhe al volo usando itertools.compress sul testo con un selettore iteratore. Il selettore restituisce True se il carattere deve essere mantenuto. selector_for_patterns crea un selettore per ogni modello. Il selettore è combinato con la funzione all (solo quando tutti i pattern vogliono mantenere un carattere che dovrebbe essere nella stringa risultante).

import itertools 
import re 

def selector_for_pattern(text, pattern): 
    i = 0 
    for m in re.finditer(pattern, text): 
     for _ in xrange(i, m.start()): 
      yield True 
     for _ in xrange(m.start(), m.end()): 
      yield False 
     i = m.end() 
    for _ in xrange(i, len(text)): 
     yield True 

def clean(text, patterns): 
    gen = [selector_for_pattern(text, pattern) for pattern in patterns] 
    selector = itertools.imap(all, itertools.izip(* gen)) 
    return "".join(itertools.compress(text, selector))