2012-06-07 15 views
7

php ha la funzione strtr:di php per pitone

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

sostituisce chiavi del dizionario in una stringa con valori corrispondenti e (importante) non sostituisce già sostituito stringhe. Un tentativo ingenuo di scrivere lo stesso in pitone:

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'}) 

rendimenti xz-x-y che non vogliamo è (bb ha fatto ancora una volta sostituito). Come cambiare la funzione sopra in modo che si comporti come la sua controparte php?

(Preferirei una risposta senza espressioni regolari, se possibile).

Aggiornamento: alcune grandi risposte qui. Li ho cronometrato e ha scoperto che per brevi stringhe la versione di Gumbo sembra essere il più veloce, in stringhe più lunghe il vincitore è la soluzione 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 

mia versione (che è leggermente ottimizzato Gumbo di):

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) 

codici e tempi completi: https://gist.github.com/2889181

risposta

3

Ecco un algoritmo ingenuo:

utilizzare un indice di camminare il personaggio stringa originale per carattere e verificare per ogni indice se una delle stringhe di ricerca è uguale alla stringa dall'indice corrente. Se viene trovata una corrispondenza, spingere la sostituzione in un buffer e procedere con l'indice per la lunghezza della stringa corrispondente. Se non viene trovata alcuna corrispondenza, procedere con l'indice di uno. Alla fine, concatena le stringhe nel buffer a una singola stringa.

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

Ci mancano entrambi (dallo strtr doc): le chiavi più lunghe verranno provate prima. –

+0

Grazie, funziona bene (tranne che per l'errore di battitura). – georg

2

str.translate è l'equivalente, ma può mappare solo ai singoli caratteri.

+0

Sì, cattivo esempio. Ho modificato la domanda. – georg

6

Di seguito utilizza espressioni regolari per farlo:

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'})) 

Come la versione del PHP, questo dà la preferenza alle partite più lunghe.

+0

No, non dà la preferenza alle corrispondenze più lunghe, dipende dall'ordine arbitrario delle chiavi del dizionario: 'strtr ('xxa-bb-cc', {'xx': 'bbz', 'xxa': 'bby' }) '->' 'bbza-bb-cc''. Usando 'sorted (repl.keys(), key = len, reverse = True)' al posto di 'repl.keys()' dovrebbe risolverlo. – Duncan

+0

@Duncan: È sorprendente, grazie per aver segnalato (ho sempre pensato che Python abbia dato la partita più lunga, ma chiaramente non lo è.) – NPE

+0

Ripetizioni 'x *', 'x +', 'x?' E 'x {m, n} 'sono tutti avidi, quindi ripeteranno' x' tanto quanto sono permessi e in grado, 'x *?', 'x +?', 'x ??', 'x {m, n}? 'sono tutti non grezzi, quindi si abbinano il più breve possibile. 'x | y' è anche non avido nel senso che se' x' corrisponde al motore non considererà nemmeno 'y'. Ecco cosa è successo qui: l'alternanza è testata rigorosamente da sinistra a destra e si ferma non appena trova una corrispondenza. – 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

Bello, questo è davvero bello –

+0

Sembra bello, ma facendo '1 + 2 + ... + len (repl) chiamate ricorsive ... Non lo so. – georg

+0

Ehi, hai chiesto una versione non regex che si comporta come php, non hai chiesto troppo. ;) (Inoltre, sospetto che copiare il dict sia peggio delle chiamate ricorsive.) – kojiro