La meilleure façon de remplacer plusieurs caractères dans une chaîne?

Réponses:

432

Remplacement de deux caractères

J'ai chronométré toutes les méthodes dans les réponses actuelles avec une supplémentaire.

Avec une chaîne d'entrée de abc&def#ghiet le remplacement et -> \ & # et -> \ #, le meilleur moyen était de chaîner les remplacements comme ceci: text.replace('&', '\&').replace('#', '\#').

Timings pour chaque fonction:

  • a) 1000000 boucles, le meilleur de 3: 1,47 μs par boucle
  • b) 1000000 boucles, le meilleur de 3: 1,51 μs par boucle
  • c) 100000 boucles, le meilleur de 3: 12,3 μs par boucle
  • d) 100000 boucles, le meilleur de 3: 12 μs par boucle
  • e) 100000 boucles, le meilleur de 3: 3,27 μs par boucle
  • f) 1000000 boucles, le meilleur de 3: 0,817 μs par boucle
  • g) 100000 boucles, le meilleur de 3: 3,64 μs par boucle
  • h) 1000000 boucles, le meilleur de 3: 0,927 μs par boucle
  • i) 1000000 boucles, le meilleur de 3: 0,814 μs par boucle

Voici les fonctions:

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'\#')

Chronométré comme ceci:

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

Remplacement de 17 caractères

Voici un code similaire pour faire de même mais avec plus de caractères à échapper (\ `* _ {}> # + -.! $):

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'\$')

Voici les résultats pour la même chaîne d'entrée abc&def#ghi:

  • a) 100000 boucles, le meilleur de 3: 6,72 μs par boucle
  • b) 100000 boucles, le meilleur de 3: 2,64 μs par boucle
  • c) 100000 boucles, le meilleur de 3: 11,9 μs par boucle
  • d) 100000 boucles, le meilleur de 3: 4,92 μs par boucle
  • e) 100000 boucles, le meilleur de 3: 2,96 μs par boucle
  • f) 100000 boucles, le meilleur de 3: 4,29 μs par boucle
  • g) 100000 boucles, le meilleur de 3: 4,68 μs par boucle
  • h) 100000 boucles, le meilleur de 3: 4,73 μs par boucle
  • i) 100000 boucles, le meilleur de 3: 4,24 μs par boucle

Et avec une chaîne d'entrée plus longue ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100000 boucles, le meilleur de 3: 7,59 μs par boucle
  • b) 100000 boucles, le meilleur de 3: 6,54 μs par boucle
  • c) 100000 boucles, le meilleur de 3: 16,9 μs par boucle
  • d) 100000 boucles, le meilleur de 3: 7,29 μs par boucle
  • e) 100000 boucles, le meilleur de 3: 12,2 μs par boucle
  • f) 100000 boucles, le meilleur de 3: 5,38 μs par boucle
  • g) 10000 boucles, le meilleur de 3: 21,7 μs par boucle
  • h) 100000 boucles, le meilleur de 3: 5,7 μs par boucle
  • i) 100000 boucles, le meilleur de 3: 5,13 μs par boucle

Ajout de quelques variantes:

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)

Avec l'entrée la plus courte:

  • ab) 100000 boucles, le meilleur de 3: 7,05 μs par boucle
  • ba) 100000 boucles, le meilleur de 3: 2,4 μs par boucle

Avec l'entrée la plus longue:

  • ab) 100000 boucles, le meilleur de 3: 7,71 μs par boucle
  • ba) 100000 boucles, le meilleur de 3: 6,08 μs par boucle

Je vais donc utiliser bapour la lisibilité et la vitesse.

Addenda

Invité par haccks dans les commentaires, une différence entre abet baest le if c in text:chèque. Testons-les contre deux autres variantes:

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)

Les temps en μs par boucle sur Python 2.7.14 et 3.6.3, et sur une machine différente de l'ensemble précédent, ne peuvent donc pas être comparés directement.

╭────────────╥──────┬───────────────┬──────┬──────────────────╮
 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          
└────────────╨──────┴───────────────┴──────┴──────────────────┘

Nous pouvons conclure que:

  • Ceux avec le chèque sont jusqu'à 4x plus rapides que ceux sans le chèque

  • ab_with_checkest légèrement en tête sur Python 3, mais ba(avec vérification) a une plus grande avance sur Python 2

  • Cependant, la plus grande leçon ici est que Python 3 est jusqu'à 3 fois plus rapide que Python 2 ! Il n'y a pas de différence énorme entre le plus lent sur Python 3 et le plus rapide sur Python 2!

Hugo
la source
4
Pourquoi n'est-ce pas la réponse exceptée?
Soupe au poulet du
Est if c in text:nécessaire dans ba?
haccks
@haccks Ce n'est pas nécessaire, mais c'est 2-3x plus rapide avec. Chaîne courte, avec: 1.45 usec per loop, et sans: 5.3 usec per loop, chaîne longue, avec: 4.38 usec per loopet sans: 7.03 usec per loop. (Notez que ceux-ci ne sont pas directement comparables aux résultats ci-dessus, car il s'agit d'une machine différente, etc.)
Hugo
1
@Hugo; Je pense que cette différence de temps est due à replaceest appelée uniquement lorsque cse trouve textdans le cas de bapendant qu'elle est appelée à chaque itération ab.
haccks
2
@haccks Merci, j'ai mis à jour ma réponse avec d'autres timings: l'ajout de la vérification est meilleur pour les deux, mais la plus grande leçon est que Python 3 est jusqu'à 3 fois plus rapide!
Hugo
73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi
ghostdog74
la source
Pourquoi une double barre oblique inverse était-elle nécessaire? Pourquoi le "\" ne fonctionne-t-il pas seulement?
axolotl
3
La double barre oblique inverse échappe à la barre oblique inverse, sinon python interpréterait "\" comme un caractère de citation littéral dans une chaîne encore ouverte.
Riet
Pourquoi en avez-vous besoin string=string.replace(ch,"\\"+ch)? N'est-ce pas juste string.replace(ch,"\\"+ch)assez?
MattSom
1
@MattSom replace () ne modifie pas la chaîne d'origine, mais renvoie une copie. Vous avez donc besoin de l'affectation pour que le code ait un effet.
Ben Brian
3
Avez-vous vraiment besoin du si? Cela ressemble à une duplication de ce que le remplacement fera de toute façon.
lorenzo
32

Enchaînez simplement les replacefonctions comme celle-ci

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

Si les remplacements vont être plus nombreux, vous pouvez le faire de cette manière générique

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

Voici une méthode python3 utilisant str.translateet str.maketrans:

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

La chaîne imprimée est abc\&def\#ghi.

tommy.carstensen
la source
2
C'est une bonne réponse, mais dans la pratique, un .translate()semble plus lent que trois chaînés .replace()(en utilisant CPython 3.6.4).
Changaco
@Changaco Merci de l'avoir chronométré 👍 En pratique, je m'utiliserais replace()moi-même, mais j'ai ajouté cette réponse par souci d'exhaustivité.
tommy.carstensen
Pour les chaînes de grande taille et de nombreux remplacements, cela devrait être plus rapide, bien que certains tests soient intéressants ...
Chiffre
Eh bien, ce n'est pas sur ma machine (même pour les remplacements 2 et 17).
Graipher
comment est '\#'valide? ça ne devrait pas être r'\#'ou '\\#'? Peut-être un problème de formatage du bloc de code.
parité3
16

Allez-vous toujours ajouter une barre oblique inverse? Si oui, essayez

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

Ce n'est peut-être pas la méthode la plus efficace mais je pense que c'est la plus simple.

kennytm
la source
15
aarrgghh tryr'\\\1'
John Machin
10

Tard dans la soirée, mais j'ai perdu beaucoup de temps avec ce problème jusqu'à ce que je trouve ma réponse.

Court et doux, translateest supérieur àreplace . Si vous êtes plus intéressé par l'optimisation de la fonctionnalité au fil du temps, n'utilisez pasreplace .

Utilisez également translatesi vous ne savez pas si le jeu de caractères à remplacer chevauche le jeu de caractères utilisé pour remplacer.

Exemple concret:

Utiliser replacevous vous attendrait naïvement à ce que l'extrait "1234".replace("1", "2").replace("2", "3").replace("3", "4")revienne "2344", mais il reviendra en fait "4444".

La traduction semble effectuer ce que l'OP souhaitait à l'origine.

Sebastialonso
la source
6

Vous pouvez envisager d'écrire une fonction d'échappement générique:

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

De cette façon, vous pouvez rendre votre fonction configurable avec une liste de caractères à échapper.

Victor Olex
la source
3

Pour info, cela est peu ou pas utile à l'OP mais il peut être utile à d'autres lecteurs (veuillez ne pas voter contre, j'en suis conscient).

Comme un exercice quelque peu ridicule mais intéressant, je voulais voir si je pouvais utiliser la programmation fonctionnelle python pour remplacer plusieurs caractères. Je suis presque sûr que cela ne bat PAS simplement en appelant replace () deux fois. Et si les performances étaient un problème, vous pourriez facilement battre cela en rouille, C, julia, perl, java, javascript et peut-être même awk. Il utilise un package «helpers» externe appelé pytoolz , accéléré via cython ( cytoolz, c'est un package 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),))))))

Je ne vais même pas expliquer cela car personne ne prendrait la peine de l'utiliser pour effectuer plusieurs remplacements. Néanmoins, je me sentais quelque peu accompli en faisant cela et pensais que cela pourrait inspirer d'autres lecteurs ou gagner un concours d'obscurcissement de code.

parité3
la source
1
«programmation fonctionnelle» ne signifie pas «utiliser autant de fonctions que possible», vous savez.
Craig Andrews
1
Il s'agit d'un remplaçant multi-caractères parfaitement fonctionnel et pur: gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Pas d'allocations, pas de mutations, pas d'effets secondaires. Lisible aussi.
Craig Andrews
1

En utilisant réduire qui est disponible en python2.7 et python3. *, Vous pouvez facilement remplacer plusieurs sous-chaînes de manière propre et pythonique.

# 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"

En python2.7 vous n'avez pas à importer de réduire mais en python3. * Vous devez l'importer depuis le module functools.

CasualCoder3
la source
1

Peut-être une simple boucle pour remplacer les caractères:

a = '&#'

to_replace = ['&', '#']

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

print(a)

>>> \&\#
Tiago Wutzke de Oliveira
la source
1

Que dis-tu de ça?

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

puis

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

production

\&\#

similaire à répondre

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

Vous souhaitez utiliser une chaîne «brute» (indiquée par le préfixe «r» de la chaîne de remplacement), car les chaînes brutes ne traitent pas spécialement la barre oblique inverse.

jonesy
la source