2008-10-15 12 views
7

sytuacja:Przechwytywanie zawartość regex i usunąć je, sprawnie

  • tekst: ciąg
  • R: regex, który pasuje część napisu. To może być kosztowne do obliczenia.

Chcę zarówno usunąć dopasowania R z tekstu, i zobaczyć, co faktycznie zawierają. Obecnie robię to tak:

import re 
ab_re = re.compile("[ab]") 
text="abcdedfe falijbijie bbbb laifsjelifjl" 
ab_re.findall(text) 
# ['a', 'b', 'a', 'b', 'b', 'b', 'b', 'b', 'a'] 
ab_re.sub('',text) 
# 'cdedfe flijijie lifsjelifjl' 

To uruchamia regex dwa razy, w pobliżu, jak mogę powiedzieć. Czy istnieje technika, aby zrobić to wszystko po przejściu, być może przy użyciu re.split? Wygląda na to, że z rozwiązaniami opartymi na podzieleniu musiałbym wykonać regex przynajmniej dwa razy.

+0

Przynajmniej 3 dobre zestawy odpowiedzi. Mój jest szybki i prosty, używając re.split(). Deestan ujawnia wszystkie informacje o meczach za pomocą programu finditer() i programów Jona Cage'a, że ​​funkcje mogą być używane w re.sub, który jest sprytny. –

Odpowiedz

4
import re 

r = re.compile("[ab]") 
text = "abcdedfe falijbijie bbbb laifsjelifjl" 

matches = [] 
replaced = [] 
pos = 0 
for m in r.finditer(text): 
    matches.append(m.group(0)) 
    replaced.append(text[pos:m.start()]) 
    pos = m.end() 
replaced.append(text[pos:]) 

print matches 
print ''.join(replaced) 

Wyjścia:

['a', 'b', 'a', 'b', 'b', 'b', 'b', 'b', 'a'] 
cdedfe flijijie lifsjelifjl 
+0

Możesz użyć listy w miejsce StringIO i dołączyć do niej na końcu, jeśli chcesz zachować prostotę. – Tomalak

+0

Tomalak: Tak, to byłoby prostsze. Trochę profilowania testowego pokazuje, że jest on rzeczywiście szybszy, przynajmniej na moim teście wejściowym. – Deestan

+0

Naprawdę mnie to nie dziwi. Pisałem w zasadzie to samo, po prostu stało się szybciej.;-) – Tomalak

0

Można użyć podziału z przechwytywaniem paranthezy. Jeśli to zrobisz, tekst wszystkich grup we wzorcu zostanie również zwrócony jako część wynikowej listy (od python doc).

Więc kod byłby

import re 
ab_re = re.compile("([ab])") 
text="abcdedfe falijbijie bbbb laifsjelifjl" 
matches = ab_re.split(text) 
# matches = ['', 'a', '', 'b', 'cdedfe f', 'a', 'lij', 'b', 'ijie ', 'b', '', 'b', '', 'b', '', 'b', ' l', 'a', 'ifsjelifjl'] 

# now extract the matches 
Rmatches = [] 
remaining = [] 
for i in range(1, len(matches), 2): 
    Rmatches.append(matches[i]) 
# Rmatches = ['a', 'b', 'a', 'b', 'b', 'b', 'b', 'b', 'a'] 

for i in range(0, len(matches), 2): 
    remaining.append(matches[i]) 
remainingtext = ''.join(remaining) 
# remainingtext = 'cdedfe flijijie lifsjelifjl' 
+0

Cały kod "if text == a" w tym miejscu implementuje wyrażenie regularne po raz drugi. Jeśli regex był prosty, jak [ab], to całe pytanie byłoby dyskusyjne. :) Dobry wysiłek, a to trochę pobudza moje myślenie, do filtrowania rozwiązań. –

+0

Tak, naprawiono je, zauważając, że dopasowania zawierają naprzemienny i odrzucony tekst, w tym puste ciągi, więc powyższe rozwiązanie jest prostsze, a wyrażenie regularne jest uruchamiane tylko raz :) –

+0

Wystarczająco dobre. Krojenie jest jednak prostsze :) Miałem taką samą świadomość o naprzemiennym dopasowaniu tokena! Dzięki za podpowiedź. –

3

Moje zmienioną odpowiedź, używając re.split(), który robi rzeczy w jeden regex przejściu:

import re 
text="abcdedfe falijbijie bbbb laifsjelifjl" 
ab_re = re.compile("([ab])") 
tokens = ab_re.split(text) 
non_matches = tokens[0::2] 
matches = tokens[1::2] 

(edit: tutaj jest pełna wersja funkcja)

def split_matches(text,compiled_re): 
    ''' given a compiled re, split a text 
    into matching and nonmatching sections 
    returns m, n_m, two lists 
    ''' 
    tokens = compiled_re.split(text) 
    matches = tokens[1::2] 
    non_matches = tokens[0::2] 
    return matches,non_matches 

m,nm = split_matches(text,ab_re) 
''.join(nm) # equivalent to ab_re.sub('',text) 
+0

Zauważ, że skompilowany re musi być "powtórnym robieniem" z parenami wokół całego bałaganu, albo to nie zadziała poprawnie. –

+0

Hmm? Działa dla mnie bez nawiasów. – Deestan

4

Co o tym:

import re 

text = "abcdedfe falijbijie bbbb laifsjelifjl" 
matches = [] 

ab_re = re.compile("[ab]") 

def verboseTest(m): 
    matches.append(m.group(0)) 
    return '' 

textWithoutMatches = ab_re.sub(verboseTest, text) 

print matches 
# ['a', 'b', 'a', 'b', 'b', 'b', 'b', 'b', 'a'] 
print textWithoutMatches 
# cdedfe flijijie lifsjelifjl 

Argument "rEPL z działalności re.sub funkcji może być funkcją, dzięki czemu można zgłasza lub zapisuje stamtąd mecze i niezależnie od tego, która funkcja zwraca, to co zastąpi "sub".

Funkcja może być łatwo zmodyfikowana, aby zrobić o wiele więcej! Aby uzyskać więcej informacji o tym, co jeszcze jest możliwe, zapoznaj się z dokumentem the re module documentation na docs.python.org.

+0

To bardzo sprytne rozwiązanie. Nie wiedziałem, że możesz użyć funkcji jako pierwszego argumentu funkcji podrzędnej. –

+0

Dzięki, byłem raczej podekscytowany jego prostotą, gdy zdałem sobie sprawę, że możesz wywołać funkcję :-) –

Powiązane problemy