5

Tengo esta cadena inicial.Optimización de cobertura de cadenas en Python

'bananaappleorangestrawberryapplepear' 

y también tienen una tupla con cadenas:

('apple', 'plepe', 'leoran', 'lemon') 

Quiero una función para que desde la cadena inicial y la tupla con cuerdas obtengo esto:

'bananaxxxxxxxxxgestrawberryxxxxxxxar' 

que sé cómo hacerlo de manera imperativa al encontrar la palabra en la cadena inicial para cada palabra y luego bucles carácter por carácter en todas las cadenas iniciales con palabras reemplazadas.

Pero no es muy eficiente y feo. Sospecho que debería haber alguna manera de hacer esto de manera más elegante, de una manera funcional, con itertools o algo así. Si conoce una biblioteca de Python que puede hacer esto de manera eficiente, por favor hágamelo saber.

ACTUALIZACIÓN: Justin Peel señaló un caso que no describí en mi pregunta inicial. Si una palabra es 'aaa' y 'aaaaaa' está en la cadena inicial, la salida debería verse como 'xxxxxx'.

Respuesta

3
import re 

words = ('apple', 'plepe', 'leoran', 'lemon') 
s = 'bananaappleorangestrawberryapplepear' 

x = set() 

for w in words: 
    for m in re.finditer(w, s): 
     i = m.start() 
     for j in range(i, i+len(w)): 
      x.add(j) 

result = ''.join(('x' if i in x else s[i]) for i in range(len(s))) 
print result 

produce:

bananaxxxxxxxxxgestrawberryxxxxxxxar 
+0

El único problema que veo con esto es el siguiente caso de uso: una de las palabras es 'aaa' y la cadena s = 'aaaaa'. Este método daría el resultado de 'xxxaa' en vez de 'xxxxx' porque 'finditer' encuentra la siguiente coincidencia no superpuesta. Probablemente no aparecerá, pero depende de lo que el OP desee. –

+0

Sí, no estaba claro para mí lo que debería suceder con la superposición de ejemplos de palabras. –

+0

@Justin No pensé en ese caso, pero en el caso de la cadena 'aaaaaa', la palabra 'aaa' debería dar 'xxxxxx'. Pero realmente es un caso de esquina, podría vivir con 'xxxaa' si hay algo mejor. –

0
a = ('apple', 'plepe', 'leoran', 'lemon') 
b = 'bananaappleorangestrawberryapplepear' 

for fruit in a: 
    if a in b: 
     b = b.replace(fruit, numberofx's) 

Lo único que tienes que hacer ahora es determinar cuántas Xs reemplazar.

+4

Esto no funcionará, ya que no garantizará una cobertura completa, p. Se superponen 'apple' y 'plepe', pero el segundo no se manejará. –

0
def mask_words(s, words): 
    mask = [False] * len(s) 
    for word in words: 
     pos = 0 
     while True: 
      idx = s.find(word, pos) 
      if idx == -1: 
       break 

      length = len(word) 
      for i in xrange(idx, idx+length): 
       mask[i] = True 
      pos = idx+length 

    # Sanity check: 
    assert len(mask) == len(s) 

    result = [] 
    for masked, c in zip(mask, s): 
     result.append('x' if masked else c) 

    return "".join(result) 
+0

No sé si esto es lo que quiere decir con "feo", pero es razonablemente rápido y comprensible. Si está procesando cadenas muy grandes con pocos accesos, podría reducir el uso de memoria al almacenar rangos para enmascarar en lugar de una matriz completa, pero el rendimiento aquí parece razonable. –

+0

'pos = idx + length' es incorrecto. Solo se debe agregar 1 a la posición, de lo contrario fallará con 'yyy' y' yyyyy'. –

1

Aquí hay otra respuesta. Puede haber una manera más rápida de reemplazar las letras con x, pero no creo que sea necesario porque esto ya es bastante rápido.

import re 

def do_xs(s,pats): 
    pat = re.compile('('+'|'.join(pats)+')') 

    sout = list(s) 
    i = 0 
    match = pat.search(s) 
    while match: 
     span = match.span() 
     sout[span[0]:span[1]] = ['x']*(span[1]-span[0]) 
     i = span[0]+1 
     match = pat.search(s,i) 
    return ''.join(sout) 

txt = 'bananaappleorangestrawberryapplepear' 
pats = ('apple', 'plepe', 'leoran', 'lemon') 
print do_xs(txt,pats) 

Básicamente, creo un patrón de expresiones regulares que coincida con cualquiera de los patrones de entrada. Luego sigo reiniciando la búsqueda comenzando 1 después de la posición inicial de la coincidencia más reciente. Sin embargo, puede haber un problema si tiene uno de los patrones de entrada es un prefijo de otro patrón de entrada.

+0

Si sabe cómo ocuparse de la carcasa con borde "xxxa", comuníqueme su solución. –

1

Asumiendo que estás limitado a trabajar sin stdlib y otras importaciones:

s1 = 'bananaappleorangestrawberryapplepear' 
t = ('apple', 'plepe', 'leoran', 'lemon') 
s2 = s1 

solution = 'bananaxxxxxxxxxgestrawberryxxxxxxxar' 

for word in t: 
    if word not in s1: continue 
    index = -1 # Start at -1 so our index search starts at 0 
    for iteration in range(s1.count(word)): 
     index = s1.find(word, index+1) 
     length = len(word) 
     before = s2[:index] 
     after = s2[index+length:] 
     s2 = before + 'x'*length + after 

print s2 == solution 
+0

De acuerdo, la restricción interna no era parte del problema, porque el OP mencionó el uso de itertools (que dudo que funcione de todos modos, ya que tenemos dos cadenas de referencia). Oh bien. – eternicode

+0

¿Sabe algo en stdlib para hacer eso fácilmente? –

+0

Es posible que pueda acortarlo con re. De otra manera no. – eternicode

1
>>> string_ = 'bananaappleorangestrawberryapplepear' 
>>> words = ('apple', 'plepe', 'leoran', 'lemon') 
>>> xes = [(string_.find(w), len(w)) for w in words] 
>>> xes 
[(6, 5), (29, 5), (9, 6), (-1, 5)] 
>>> for index, len_ in xes: 
... if index == -1: continue 
... string_ = string_.replace(string_[index:index+len_], 'x'*len_) 
... 
>>> string_ 
'bananaxxxxxxxxxgestrawberryxxxxxxxar' 
>>> 

seguramente hay maneras más eficaces, pero la optimización prematura es la raíz de todos los males.