2012-07-11 29 views
7

Estoy tratando de eliminar algunas cosas de un bloque de texto usando expresiones regulares. Tengo todos mis patrones listos, pero parece que no puedo eliminar dos (o más) que se superponen.Combinación de varias sustituciones de expresiones regulares

Por ejemplo:

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 ' 

¿Cómo reemplazo tanto de los sucesos simultáneamente y terminan con una cadena vacía?


Terminé usando una versión ligeramente modificada de 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) 

Respuesta

12

No puede hacerlo con llamadas re.sub consecutivas como lo ha demostrado. Puede usar re.finditer para encontrarlos todos. Cada partida le proporcionará un objeto de coincidencia, que tiene atributos .start y .end que indican sus posiciones. Puede recopilar todos esos elementos y luego eliminar los caracteres al final.

Aquí uso un bytearray como una cadena mutable, que se utiliza como máscara. Se inicializa a cero bytes, y marcó con una 'x' todos los bytes que coinciden con cualquier expresión regular. Luego uso la máscara de bits para seleccionar los caracteres que tener en la cadena original, y construir una nueva cadena con sólo los caracteres no coincidentes:

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

Nunca pensé en los atributos 'start' y' end' para los objetos de coincidencia. Estoy más que seguro de que esto funcionará, ¡así que gracias! – Blender

+1

¡Gran respuesta! Agregué '()' a 'inicio' y' fin', porque estos son métodos, no atributos. – georg

+0

@ thg435: gracias, debería haberlo probado! :) –

2

no ser una decepción, pero la respuesta corta es que estoy bastante seguro de no se puede. ¿Puedes cambiar tu expresión regular para que no exista superposición?

Si aún desea hacer esto, trataría de mantener un registro de los índices de inicio y detención de cada coincidencia realizada en la cadena original. Luego revisa la cadena y solo mantienes los caracteres que no están en ningún rango de eliminación.

1

bastante eficiente también es una solución proviene de Perl ... combinar las expresiones regulares en uno:

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

expresión regular a montar toma todas las variantes de expresiones regulares o cadena que desea igualar y luego ellos se combinan en uno. Y sí que cambia el problema inicial a otro ya que no es sobre la correspondencia de expresión regular se superponen más, pero la combinación de expresiones regulares para un partido

y entonces usted puede utilizarlo en su código:

$ 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 puerto de Regexp :: Assemble en python sería bueno :)

+0

El comando es 'aptitude install libregexp-assemble-perl'. No pude encontrar rápidamente ningún rastro de un paquete anterior con el nombre que indicaste, pero tal vez estés en una distribución diferente; esto es para Debian estable. – tripleee

+0

Además, en las versiones anteriores del paquete, la demostración era sólo en '/ usr/share/doc/libregexp a montar-perl/ejemplos/assemble.gz' - quería esto en un cuadro' squeeze', donde la secuencia de comandos no está instalado con el nombre que indica. – tripleee

1

Aquí hay una alternativa que filtra las cadenas sobre la marcha usando itertools.compress en el texto con un selector iterador. El selector devuelve True si el carácter debe mantenerse. selector_for_patterns crea un selector para cada patrón. El selector se combina con la función all (solo cuando todo el patrón desea mantener un carácter debe estar en la cadena resultante).

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