Il modo migliore per sostituire più caratteri in una stringa?


Risposte:


435

Sostituzione di due caratteri

Ho cronometrato tutti i metodi nelle risposte correnti insieme a uno in più.

Con una stringa di input di abc&def#ghie sostituzione e -> \ & e # -> \ #, il modo più veloce è stato quello di concatenare le sostituzioni come questo: text.replace('&', '\&').replace('#', '\#').

Tempi per ciascuna funzione:

  • a) 1000000 loop, meglio di 3: 1,47 μs per loop
  • b) 1000000 loop, meglio di 3: 1,51 μs per loop
  • c) 100000 loop, meglio di 3: 12,3 μs per loop
  • d) 100000 loop, meglio di 3: 12 μs per loop
  • e) 100000 loop, meglio di 3: 3,27 μs per loop
  • f) 1000000 loop, meglio di 3: 0,817 μs per loop
  • g) 100000 loop, meglio di 3: 3,64 μs per loop
  • h) 1000000 loop, meglio di 3: 0,927 μs per loop
  • i) 1000000 loop, meglio di 3: 0,814 μs per loop

Ecco le funzioni:

def a(text):
    chars = "&#"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['&','#']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([&#])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('&#')
def e(text):
    esc(text)


def f(text):
    text = text.replace('&', '\&').replace('#', '\#')


def g(text):
    replacements = {"&": "\&", "#": "\#"}
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('&', r'\&')
    text = text.replace('#', r'\#')


def i(text):
    text = text.replace('&', r'\&').replace('#', r'\#')

Cronometrato in questo modo:

python -mtimeit -s"import time_functions" "time_functions.a('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.b('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.c('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.d('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.e('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.f('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.g('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.h('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.i('abc&def#ghi')"

Sostituzione di 17 caratteri

Ecco un codice simile per fare lo stesso, ma con più caratteri per fuggire (\ `* _ {}> # + -.! $):

def a(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([\\`*_{}[]()>#+-.!$])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('\\`*_{}[]()>#+-.!$')
def e(text):
    esc(text)


def f(text):
    text = text.replace('\\', '\\\\').replace('`', '\`').replace('*', '\*').replace('_', '\_').replace('{', '\{').replace('}', '\}').replace('[', '\[').replace(']', '\]').replace('(', '\(').replace(')', '\)').replace('>', '\>').replace('#', '\#').replace('+', '\+').replace('-', '\-').replace('.', '\.').replace('!', '\!').replace('$', '\$')


def g(text):
    replacements = {
        "\\": "\\\\",
        "`": "\`",
        "*": "\*",
        "_": "\_",
        "{": "\{",
        "}": "\}",
        "[": "\[",
        "]": "\]",
        "(": "\(",
        ")": "\)",
        ">": "\>",
        "#": "\#",
        "+": "\+",
        "-": "\-",
        ".": "\.",
        "!": "\!",
        "$": "\$",
    }
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('\\', r'\\')
    text = text.replace('`', r'\`')
    text = text.replace('*', r'\*')
    text = text.replace('_', r'\_')
    text = text.replace('{', r'\{')
    text = text.replace('}', r'\}')
    text = text.replace('[', r'\[')
    text = text.replace(']', r'\]')
    text = text.replace('(', r'\(')
    text = text.replace(')', r'\)')
    text = text.replace('>', r'\>')
    text = text.replace('#', r'\#')
    text = text.replace('+', r'\+')
    text = text.replace('-', r'\-')
    text = text.replace('.', r'\.')
    text = text.replace('!', r'\!')
    text = text.replace('$', r'\$')


def i(text):
    text = text.replace('\\', r'\\').replace('`', r'\`').replace('*', r'\*').replace('_', r'\_').replace('{', r'\{').replace('}', r'\}').replace('[', r'\[').replace(']', r'\]').replace('(', r'\(').replace(')', r'\)').replace('>', r'\>').replace('#', r'\#').replace('+', r'\+').replace('-', r'\-').replace('.', r'\.').replace('!', r'\!').replace('$', r'\$')

Ecco i risultati per la stessa stringa di input abc&def#ghi:

  • a) 100000 loop, meglio di 3: 6,72 μs per loop
  • b) 100000 loop, meglio di 3: 2,64 μs per loop
  • c) 100000 loop, meglio di 3: 11,9 μs per loop
  • d) 100000 loop, meglio di 3: 4,92 μs per loop
  • e) 100000 loop, meglio di 3: 2,96 μs per loop
  • f) 100000 loop, meglio di 3: 4,29 μs per loop
  • g) 100000 loop, meglio di 3: 4.68 μs per loop
  • h) 100000 loop, meglio di 3: 4,73 μs per loop
  • i) 100000 loop, meglio di 3: 4,24 μs per loop

E con una stringa di input più lunga ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100000 loop, meglio di 3: 7,59 μs per loop
  • b) 100000 loop, meglio di 3: 6,54 μs per loop
  • c) 100000 loop, meglio di 3: 16,9 μs per loop
  • d) 100000 loop, meglio di 3: 7,29 μs per loop
  • e) 100000 loop, meglio di 3: 12,2 μs per loop
  • f) 100000 loop, meglio di 3: 5,38 μs per loop
  • g) 10000 loop, meglio di 3: 21,7 μs per loop
  • h) 100000 loop, meglio di 3: 5,7 μs per loop
  • i) 100000 loop, meglio di 3: 5,13 μs per loop

Aggiunta di un paio di varianti:

def ab(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        text = text.replace(ch,"\\"+ch)


def ba(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        if c in text:
            text = text.replace(c, "\\" + c)

Con l'input più breve:

  • ab) 100000 loop, meglio di 3: 7,05 μs per loop
  • ba) 100000 loop, meglio di 3: 2,4 μs per loop

Con l'input più lungo:

  • ab) 100000 loop, meglio di 3: 7,71 μs per loop
  • ba) 100000 loop, meglio di 3: 6,08 μs per loop

Quindi userò baper leggibilità e velocità.

appendice

Spinto da hacck nei commenti, una differenza tra abe baè il if c in text:controllo. Proviamoli con altre due varianti:

def ab_with_check(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)

def ba_without_check(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)

Tempi in μs per loop su Python 2.7.14 e 3.6.3 e su una macchina diversa dal set precedente, quindi non possono essere confrontati direttamente.

╭────────────╥──────┬───────────────┬──────┬──────────────────╮
 Py, input    ab   ab_with_check   ba   ba_without_check 
╞════════════╬══════╪═══════════════╪══════╪══════════════════╡
 Py2, short  8.81     4.22        3.45     8.01          
 Py3, short  5.54     1.34        1.46     5.34          
├────────────╫──────┼───────────────┼──────┼──────────────────┤
 Py2, long   9.3      7.15        6.85     8.55          
 Py3, long   7.43     4.38        4.41     7.02          
└────────────╨──────┴───────────────┴──────┴──────────────────┘

Possiamo concludere che:

  • Quelli con il controllo sono fino a 4 volte più veloci di quelli senza controllo

  • ab_with_checkè leggermente in vantaggio su Python 3, ma ba(con segno di spunta) ha un vantaggio maggiore su Python 2

  • Tuttavia, la lezione più grande qui è che Python 3 è fino a 3 volte più veloce di Python 2 ! Non c'è un'enorme differenza tra il più lento su Python 3 e il più veloce su Python 2!


4
Perché questa non è la risposta esclusa?
Chicken Soup

È if c in text:necessario in ba?
Hawcks

@haccks Non è necessario, ma è 2-3 volte più veloce. Stringa corta, con: 1.45 usec per loope senza: 5.3 usec per loop, stringa lungo, con: 4.38 usec per loope senza: 7.03 usec per loop. (Nota che questi non sono direttamente confrontabili con i risultati sopra, perché è una macchina diversa, ecc.)
Hugo,

1
@Hugo; Penso che questa differenza nel tempo sia dovuta al fatto che replaceviene chiamata solo quando cviene trovata textnel caso in cui baviene chiamata in ogni iterazione in ab.
Hawcks

2
@haccks Grazie, ho aggiornato la mia risposta con ulteriori tempistiche: aggiungere il controllo è migliore per entrambi, ma la lezione più grande è che Python 3 è fino a 3 volte più veloce!
Hugo,

73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi

Perché era necessaria una doppia barra rovesciata? Perché il "\" non funziona?
axolotl,

3
La doppia barra rovesciata sfugge alla barra rovesciata, altrimenti Python interpreterebbe "\" come un carattere di citazione letterale all'interno di una stringa ancora aperta.
Riet,

Perché ne hai bisogno string=string.replace(ch,"\\"+ch)? Non è string.replace(ch,"\\"+ch)abbastanza?
MattSom,

1
@MattSom replace () non modifica la stringa originale, ma restituisce una copia. Quindi è necessario che l'assegnazione del codice abbia alcun effetto.
Ben Brian,

3
Hai davvero bisogno del se? Sembra una duplicazione di ciò che il sostituto farà comunque.
lorenzo,

32

Incatenate semplicemente le replacefunzioni in questo modo

strs = "abc&def#ghi"
print strs.replace('&', '\&').replace('#', '\#')
# abc\&def\#ghi

Se le sostituzioni saranno più numerose, puoi farlo in questo modo generico

strs, replacements = "abc&def#ghi", {"&": "\&", "#": "\#"}
print "".join([replacements.get(c, c) for c in strs])
# abc\&def\#ghi

30

Ecco un metodo python3 usando str.translatee str.maketrans:

s = "abc&def#ghi"
print(s.translate(str.maketrans({'&': '\&', '#': '\#'})))

La stringa stampata è abc\&def\#ghi.


2
Questa è una buona risposta, ma in pratica farlo .translate()sembra essere più lento di tre concatenati .replace()(usando CPython 3.6.4).
Changaco,

@Changaco Grazie per il tempismo 👍 In pratica mi userei replace(), ma ho aggiunto questa risposta per completezza.
tommy.carstensen,

Per stringhe di grandi dimensioni e molti sostituti questo dovrebbe essere più veloce, anche se alcuni test sarebbero carini ...
Graipher

Bene, non è sulla mia macchina (lo stesso per le sostituzioni 2 e 17).
Graipher

come è '\#'valido? non dovrebbe essere r'\#'o '\\#'? Potrebbe essere forse un problema di formattazione del blocco di codice.
parità 3

16

Hai sempre intenzione di anteporre una barra rovesciata? Se è così, prova

import re
rx = re.compile('([&#])')
#                  ^^ fill in the characters here.
strs = rx.sub('\\\\\\1', strs)

Potrebbe non essere il metodo più efficiente ma penso che sia il più semplice.


15
aarrgghh tryr'\\\1'
John Machin,

10

In ritardo alla festa, ma ho perso molto tempo con questo problema fino a quando non ho trovato la mia risposta.

Breve e dolce, translateè superiore areplace . Se sei più interessato alla funzionalità nel tempo, non utilizzare replace.

Utilizzare anche translatese non si sa se l'insieme di caratteri da sostituire si sovrappone all'insieme di caratteri utilizzato per sostituire.

Caso in questione:

Usandoti replaceti aspetteresti ingenuamente che lo snippet "1234".replace("1", "2").replace("2", "3").replace("3", "4")ritorni "2344", ma tornerà di fatto "4444".

La traduzione sembra eseguire ciò che OP inizialmente desiderava.


6

Puoi considerare di scrivere una funzione di escape generica:

def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])

>>> esc = mk_esc('&#')
>>> print esc('Learn & be #1')
Learn \& be \#1

In questo modo è possibile rendere configurabile la propria funzione con un elenco di caratteri da evitare.


3

Cordiali saluti, questo è di scarsa utilità per l'OP, ma potrebbe essere utile per altri lettori (per favore non sottovalutare, ne sono consapevole).

Come esercizio un po 'ridicolo ma interessante, volevo vedere se potevo usare la programmazione funzionale di Python per sostituire più caratteri. Sono abbastanza sicuro che questo NON batte solo chiamando due volte (). E se le prestazioni fossero un problema, potresti facilmente battere in ruggine, C, julia, perl, java, javascript e forse anche awk. Utilizza un pacchetto esterno di "aiutanti" chiamato pytoolz , accelerato via cython ( cytoolz, è un pacchetto pypi ).

from cytoolz.functoolz import compose
from cytoolz.itertoolz import chain,sliding_window
from itertools import starmap,imap,ifilter
from operator import itemgetter,contains
text='&hello#hi&yo&'
char_index_iter=compose(partial(imap, itemgetter(0)), partial(ifilter, compose(partial(contains, '#&'), itemgetter(1))), enumerate)
print '\\'.join(imap(text.__getitem__, starmap(slice, sliding_window(2, chain((0,), char_index_iter(text), (len(text),))))))

Non lo spiegherò nemmeno perché nessuno si preoccuperebbe di usarlo per realizzare la sostituzione multipla. Tuttavia, mi sono sentito in qualche modo realizzato nel fare questo e ho pensato che potesse ispirare altri lettori o vincere un concorso di offuscamento del codice.


1
"programmazione funzionale" non significa "usare quante più funzioni possibili", sai.
Craig Andrews,

1
Questo è un sostituto multi-carattere perfettamente funzionante e puro: gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Nessuna allocazione, nessuna mutazione, nessun effetto collaterale. Leggibile anche.
Craig Andrews,

1

Usando riduci che è disponibile in python2.7 e python3. * Puoi facilmente sostituire sottostringhe multiple in modo pulito e pitonico.

# Lets define a helper method to make it easy to use
def replacer(text, replacements):
    return reduce(
        lambda text, ptuple: text.replace(ptuple[0], ptuple[1]), 
        replacements, text
    )

if __name__ == '__main__':
    uncleaned_str = "abc&def#ghi"
    cleaned_str = replacer(uncleaned_str, [("&","\&"),("#","\#")])
    print(cleaned_str) # "abc\&def\#ghi"

In python2.7 non devi importare ridurre ma in python3. * Devi importarlo dal modulo functools.


1

Forse un semplice ciclo per i caratteri da sostituire:

a = '&#'

to_replace = ['&', '#']

for char in to_replace:
    a = a.replace(char, "\\"+char)

print(a)

>>> \&\#

1

Cosa ne pensi di questo?

def replace_all(dict, str):
    for key in dict:
        str = str.replace(key, dict[key])
    return str

poi

print(replace_all({"&":"\&", "#":"\#"}, "&#"))

produzione

\&\#

simile alla risposta


0
>>> a = '&#'
>>> print a.replace('&', r'\&')
\&#
>>> print a.replace('#', r'\#')
&\#
>>> 

Si desidera utilizzare una stringa 'raw' (indicata dalla 'r' che precede la stringa di sostituzione), poiché le stringhe raw non trattano la barra rovesciata in modo speciale.


0

modo avanzato usando regex

import re
text = "hello ,world!"
replaces = {"hello": "hi", "world":" 2020", "!":"."}
regex = re.sub("|".join(replaces.keys()), lambda match: replaces[match.string[match.start():match.end()]], text)
print(regex)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.