2012-06-07 16 views
7

php tiene la función strtr:de php para el pitón

strtr('aa-bb-cc', array('aa' => 'bbz', 'bb' => 'x', 'cc' => 'y')); 
# bbz-x-y 

Sustituye claves de diccionario en una cadena con los valores correspondientes y (importante) no reemplaza ya reemplazado cuerdas. Un intento ingenuo para escribir la misma en Python:

def strtr(strng, replace): 
    for s, r in replace.items(): 
     strng = strng.replace(s, r) 
    return strng 

strtr('aa-bb-cc', {'aa': 'bbz', 'bb': 'x', 'cc': 'y'}) 

vuelve xz-x-y que no queremos es (bb obtuve reemplazado de nuevo). ¿Cómo cambiar la función anterior para que se comporte como su contraparte php?

(Preferiría una respuesta sin expresiones regulares, si es posible).

Upd: algunas buenas respuestas aquí. Me ellos cronometrados y se encontró que para las cadenas cortas versión de Gumbo parece ser el más rápido, en cadenas más largas el ganador es la solución re:

# 'aa-bb-cc' 
0.0258 strtr_thg 
0.0274 strtr_gumbo 
0.0447 strtr_kojiro 
0.0701 strtr_aix 

# 'aa-bb-cc'*10 
0.1474 strtr_aix 
0.2261 strtr_thg 
0.2366 strtr_gumbo 
0.3226 strtr_kojiro 

Mi propia versión (que está ligeramente optimizado Gumbo):

def strtr(strng, replace): 
    buf, i = [], 0 
    while i < len(strng): 
     for s, r in replace.items(): 
      if strng[i:len(s)+i] == s: 
       buf.append(r) 
       i += len(s) 
       break 
     else: 
      buf.append(strng[i]) 
      i += 1 
    return ''.join(buf) 

códigos y tiempos completos: https://gist.github.com/2889181

Respuesta

3

Aquí es un algoritmo ingenuo:

utilizar un índice de caminar por la cadena de caracteres original de carácter y comprobar para cada índice si una de las cadenas de búsqueda es igual a la cadena a partir del índice actual sobre. Si se encuentra una coincidencia, inserte el reemplazo en un búfer y proceda con el índice por la longitud de la secuencia coincidente. Si no se encuentra ninguna coincidencia, proceda con el índice en uno. Al final, concatene las cadenas en el buffer a una sola cadena.

def strtr(strng, replace): 
    buffer = [] 
    i, n = 0, len(strng) 
    while i < n: 
     match = False 
     for s, r in replace.items(): 
      if strng[i:len(s)+i] == s: 
       buffer.append(r) 
       i = i + len(s) 
       match = True 
       break 
     if not match: 
      buffer.append(strng[i]) 
      i = i + 1 
    return ''.join(buffer) 
+0

Ambos nos falta esto (del documento strtr): primero se probarán las claves más largas. –

+0

Gracias, esto funciona bien (excepto por el error tipográfico). – georg

2

str.translate es el equivalente, pero sólo se puede asignar a un solo carácter.

+0

Sí, mal ejemplo. Edité la pregunta. – georg

6

El siguiente utiliza expresiones regulares para hacerlo:

import re 

def strtr(s, repl): 
    pattern = '|'.join(map(re.escape, sorted(repl, key=len, reverse=True))) 
    return re.sub(pattern, lambda m: repl[m.group()], s) 

print(strtr('aa-bb-cc', {'aa': 'bbz', 'bb': 'x', 'cc': 'y'})) 

Al igual que la versión del PHP, esto da preferencia a los partidos más largos.

+0

No, no da preferencia a las coincidencias más largas, depende del orden arbitrario de las teclas del diccionario: 'strtr ('xxa-bb-cc', {'xx': 'bbz', 'xxa': 'bby' }) '->''bbza-bb-cc''. Usar 'sorted (repl.keys(), key = len, reverse = True)' en lugar de 'repl.keys()' debería arreglar eso. – Duncan

+0

@Duncan: qué sorprendente, gracias por señalar (siempre pensé que el 're' de Python daba la coincidencia más larga, pero claramente no es así) – NPE

+0

Repeticiones' x * ',' x + ',' x? 'Y' x {m, n} 'son todos codiciosos, por lo que repetirán' x' tanto como estén permitidos y sean capaces, 'x *?', 'x +?', 'x ??', 'x {m, n}? 'no son codiciosos, por lo que coinciden lo más breve posible. 'x | y' tampoco es codicioso en el sentido de que si' x' coincide con el motor ni siquiera considerará 'y'. Eso es lo que sucedió aquí: la alternancia se prueba estrictamente de izquierda a derecha y se detiene tan pronto como encuentra una coincidencia. – Duncan

5
def strtr(strng, replace): 
    if replace and strng: 
     s, r = replace.popitem() 
     return r.join(strtr(subs, dict(replace)) for subs in strng.split(s)) 
    return strng 

j=strtr('aa-bb-cc', {'aa': 'bbz', 'bb': 'x', 'cc': 'y'}) 
assert j=='bbz-x-y', j 
+0

Bien, esto es genial –

+0

Parece genial, pero haciendo llamadas recursivas '1 + 2 + ... + len (repl)' ... No lo sé. – georg

+0

Oye, solicitaste una versión no regex que se comporta como php, no la pediste rápido. ;) (Además, sospecho que copiar el dict es peor que las llamadas recursivas.) – kojiro