2010-07-29 4 views
6

Se si desidera eseguire più sostituzioni di stringa, qual è il modo più efficiente per eseguire questa operazione?Eseguire in modo efficiente più sostituzioni di stringhe in Python

Un esempio del tipo di situazione che ho incontrato nei miei viaggi è la seguente:

>>> strings = ['a', 'list', 'of', 'strings'] 
>>> [s.replace('a', '')...replace('u', '') for s in strings if len(s) > 2] 
['a', 'lst', 'of', 'strngs'] 
+0

Non sono sicuro di come si intende utilizzare una tabella di ricerca: quali sono le chiavi e i valori? Inoltre, il tuo esempio ha alcuni refusi/incongruenze. – zdav

+0

Dove sono gli errori di battitura? Ho usato un'ellissi per troncare l'esempio per la leggibilità. –

risposta

9

L'esempio specifico si danno (l'eliminazione di singoli caratteri) è perfetto per il metodo translate di stringhe, come la sostituzione di singoli caratteri con caratteri singoli. Se la stringa di input è di tipo Unicode, quindi, oltre ai due suddetti tipi di "sostituzione", la sostituzione di singoli caratteri con più stringhe di caratteri è ugualmente valida con il metodo translate (non se è necessario lavorare su stringhe di byte, sebbene).

Se è necessario sostituire sottostringhe di più caratteri, quindi consiglierei anche utilizzando un'espressione regolare - anche se non in risposta al modo di @ gnibbler raccomanda; piuttosto, vorrei costruire la regex da r'onestring|another|yetanother|orthis' (unire le sottostringhe che si desidera sostituire con barre verticali - assicuratevi di anche re.escape se contengono caratteri speciali, ovviamente) e scrivete una semplice funzione di sostituzione basata su un dict.

Non ho intenzione di offrire un sacco di codice in questo momento poiché non so quale dei due paragrafi si applica alle vostre reali esigenze, ma (quando torno più tardi a casa e controllo di nuovo ;-) Sarò lieto di modificare per aggiungere un esempio di codice in base alle tue modifiche alla tua domanda (più utile dei commenti a questa risposta ;-).

Edit: in un commento del PO dice che vuole una risposta "più generale" (senza chiarire che cosa ciò significhi), poi in una modifica del suo Q dice che vuole studiare i "compromessi" tra i vari frammenti tutti i dei quali usano sottostringhe a carattere singolo (e ne controllano la presenza, piuttosto che la sostituzione come richiesto originariamente - semantica completamente diversa, ovviamente).

Dato questo totale e completa confusione tutto quello che posso dire è che al "check compromessi" (performance-saggio) Mi piace usare python -mtimeit -s'setup things here' 'statements to check' (assicurandosi che le dichiarazioni di controllare non hanno effetti collaterali, per non inficiare le misure di tempo, dal momento che timeit loop implicitamente per fornire misurazioni precise della tempistica).

una risposta generale (senza compromessi, e coinvolgendo sottostringhe multiple caratteri, in modo del tutto contrario alla sua modifica di Q, ma consonanti per i suoi commenti - i due essendo del tutto contraddittorio che è ovviamente impossibile da soddisfare entrambi): uso

import re 

class Replacer(object): 

    def __init__(self, **replacements): 
    self.replacements = replacements 
    self.locator = re.compile('|'.join(re.escape(s) for s in replacements)) 

    def _doreplace(self, mo): 
    return self.replacements[mo.group()] 

    def replace(self, s): 
    return self.locator.sub(self._doreplace, s) 

esempio:

r = Replacer(zap='zop', zip='zup') 
print r.replace('allazapollezipzapzippopzip') 

Se alcune delle stringhe da sostituire sono le parole chiave di Python, hanno bisogno di essere passati in un po 'meno direttamente, ad esempio,, Il seguente:

r = Replacer(abc='xyz', def='yyt', ghi='zzq') 

sarebbe falliscono perché def è una parola chiave, quindi è necessario ad es .:

r = Replacer(abc='xyz', ghi='zzq', **{'def': 'yyt'}) 

o simili.

Trovo che questo sia un buon uso per una classe (piuttosto che una programmazione procedurale) perché la RE per individuare le sottostringhe da sostituire, il dict che esprime cosa sostituirle e il metodo che esegue la sostituzione, in realtà grida di essere "mantenuto tutto insieme", e un'istanza di classe è il modo giusto per eseguire un "mantenimento insieme" in Python. Una fabbrica chiusura avrebbe anche funzionato (in quanto il metodo replace è in realtà l'unica parte dell'istanza che esigenze siano visibili "fuori"), ma in un forse meno evidente, più difficili da eseguire il debug modo:

def make_replacer(**replacements): 
    locator = re.compile('|'.join(re.escape(s) for s in replacements)) 

    def _doreplace(mo): 
    return replacements[mo.group()] 

    def replace(s): 
    return locator.sub(_doreplace, s) 

    return replace 

r = make_replacer(zap='zop', zip='zup') 
print r('allazapollezipzapzippopzip') 

L'unico vero vantaggio potrebbe essere una prestazione molto modestamente migliore (deve essere controllato con timeit su "casi di riferimento" considerati significativi e rappresentativi per l'app che lo utilizza) come accesso alle "variabili libere" (replacements, locator, _doreplace) in questo caso potrebbe essere molto più veloce dell'accesso ai nomi qualificati (self.replacements ecc.) nel normale approccio basato sulla classe (se questo è il caso dipenderà dall'implementazione Python in uso, da cui la necessità di verificare con timeit su benchmark significativi!).

+0

Grazie per la tua risposta dettagliata Alex. Stavo davvero cercando una risposta più generale. Mi dispiace per non essere chiara con la domanda. Modificherò la Q per riflettere questo. –

0

Potreste scoprire che è più veloce di creare una regex e fare tutte le sostituzioni in una sola volta.

anche una buona idea per spostare il codice sostitutivo fuori a una funzione in modo da poter Memoize se si rischia di avere duplicati nella lista

>>> import re 
>>> [re.sub('[aeiou]','',s) for s in strings if len(s) > 2] 
['a', 'lst', 'of', 'strngs'] 


>>> def replacer(s, memo={}): 
... if s not in memo: 
...  memo[s] = re.sub('[aeiou]','',s) 
... return memo[s] 
... 
>>> [replacer(s) for s in strings if len(s) > 2] 
['a', 'lst', 'of', 'strngs'] 
+0

Sei in grado di espandere questo? 'Re.sub ('[aeiou]', '', s)' sostituisce tutto allo stesso tempo? Se sta controllando char in char, ero preoccupato dell'immutabilità della stringa Python. –

+0

@Tim, Yes, '[aeiou]' sostituisce tutte le vocali contemporaneamente. L'immutabilità non è un problema poiché stai creando nuove stringhe. –

+0

@Tim Le parentesi '[]' sono una classe di caratteri, ne combina uno qualsiasi e sostituisce quel carattere con la stringa di sostituzione. Questa soluzione è utile se: tutte le sostituzioni sono uguali e se si stanno solo abbinando singoli caratteri. – zdav