Remplacer plusieurs caractères par Python
Je dois remplacer certains caractères comme suit: &
-> \&
, #
-> \#
, ...
J'ai codé comme suit, mais je suppose qu'il devrait y avoir un meilleur moyen. Des allusions?
strs = strs.replace('&', '\&')
strs = strs.replace('#', '\#')
...
Remplacement de deux personnages
J'ai chronométré toutes les méthodes dans les réponses actuelles avec un supplémentaire.
Avec une chaîne d'entrée de abc&def#ghi
et en remplaçant les caractères & ->\& et # -> #, le plus rapide était d'enchaîner les remplacements tels que: text.replace('&', '\&').replace('#', '\#')
.
Timings pour chaque fonction:
- a) 1000000 boucles, meilleur de 3: 1,47 μs par boucle
- b) 1000000 boucles, meilleur de 3: 1,51 μs par boucle
- c) 100 000 boucles, au mieux 3: 12,3 μs par boucle
- d) 100 000 boucles, au mieux 3: 12 μs par boucle
- e) 100 000 boucles, au mieux 3: 3,27 μs par boucle
- f) 1000000 boucles, meilleur de 3: 0,817 μs par boucle
- g) 100 000 boucles, meilleur de 3: 3,64 μs par boucle
- h) 1000000 boucles, meilleur de 3: 0,927 µs par boucle
- i) 1000000 boucles, 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 la même chose 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) 100 000 boucles, au mieux 3: 6,72 μs par boucle
- b) 100000 boucles, meilleur de 3: 2,64 μs par boucle
- c) 100 000 boucles, au mieux 3: 11,9 μs par boucle
- d) 100 000 boucles, au mieux de 3: 4,92 µs par boucle
- e) 100000 boucles, meilleur de 3: 2,96 µs par boucle
- f) 100 000 boucles, meilleur de 3: 4,29 μs par boucle
- g) 100 000 boucles, meilleur de 3: 4,68 µs par boucle
- h) 100 000 boucles, meilleure des 3: 4,73 μs par boucle
- i) 100 000 boucles, meilleur de 3: 4,24 μs par boucle
Et avec une chaîne de saisie plus longue (## *Something* and [another] thing in a longer sentence with {more} things to replace$
):
- a) 100 000 boucles, au mieux 3: 7,59 μs par boucle
- b) 100 000 boucles, au mieux de 3: 6,54 μs par boucle
- c) 100 000 boucles, le meilleur des 3: 16,9 μs par boucle
- d) 100 000 boucles, meilleure des 3: 7,29 μs par boucle
- e) 100 000 boucles, meilleur de 3: 12,2 μs par boucle
- f) 100000 boucles, meilleur de 3: 5,38 µs par boucle
- g) 10000 boucles, meilleur de 3: 21,7 μs par boucle
- h) 100000 boucles, meilleur de 3: 5,7 μs par boucle
- i) 100000 boucles, 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) 100 000 boucles, meilleur de 3: 7,05 μs par boucle
- ba) 100000 boucles, meilleur de 3: 2,4 μs par boucle
Avec l'entrée la plus longue:
- ab) 100 000 boucles, meilleur de 3: 7,71 μs par boucle
- ba) 100 000 boucles, meilleur de 3: 6,08 μs par boucle
Je vais donc utiliser ba
pour la lisibilité et la rapidité.
Addenda
Invité par les haccks dans les commentaires, une différence entre ab
et ba
est le contrôle if c in text:
. 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 exprimés en μs par boucle sur Python 2.7.14 et 3.6.3, ainsi que sur un ordinateur différent de celui du jeu 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 rapide que ceux sans le chèque
ab_with_check
est légèrement en avance sur Python 3, maisba
(avec contrôle) a une avance plus grande sur Python 2Cependant, la plus grande leçon ici est Python 3 est jusqu'à 3 fois plus rapide que Python 2! Il n'y a pas une énorme différence entre le plus lent sur Python 3 et le plus rapide sur Python 2!
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
... if ch in string:
... string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi
Chaînez simplement les fonctions replace
comme ceci
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
Voici une méthode python3 utilisant str.translate
et str.maketrans
:
_s = "abc&def#ghi"
print(s.translate(str.maketrans({'&': '\&', '#': '\#'})))
_
La chaîne imprimée est _abc\&def\#ghi
_.
Allez-vous toujours prévoir 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.
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 configurer votre fonction avec une liste de caractères à échapper.
En retard à la fête, mais j'ai perdu beaucoup de temps avec cette question jusqu'à ce que j'ai trouvé ma réponse.
Court et doux, translate
est supérieur à replace
. Si vous êtes plus intéressé par l'optimisation de la fonctionnalité dans le temps, n'utilisez pas replace
.
Utilisez également translate
si vous ne savez pas si le jeu de caractères à remplacer chevauche le jeu de caractères à remplacer.
Exemple:
En utilisant replace
, vous vous attendriez naïvement à ce que l'extrait de code "1234".replace("1", "2").replace("2", "3").replace("3", "4")
renvoie "2344"
, mais il renverra en fait "4444"
.
La traduction semble réaliser ce que l’opérateur avait initialement souhaité.
Pour votre information, cela n’a que peu ou pas d’utilisation pour le PO mais peut être utile pour les autres lecteurs (veuillez ne pas abaisser le vote, je suis au courant de cela).
Comme exercice un peu ridicule mais intéressant, je voulais voir si je pouvais utiliser la programmation fonctionnelle python pour remplacer plusieurs caractères. Je suis sûr que cela ne bat PAS d'appeler simplement en remplaçant () deux fois. Et si les performances étaient un problème, vous pourriez facilement le battre avec Rust, C, Julia, Perl, Java, javascript et peut-être même awk. Il utilise un paquet 'helpers' externe appelé pytoolz , accéléré via cython ( cytoolz, c'est un paquet 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 parce que personne ne se soucierait de l'utiliser pour effectuer plusieurs remplacements. Néanmoins, je me suis senti un peu accompli en faisant cela et j'ai pensé que cela pourrait inspirer d’autres lecteurs ou gagner un concours d’obfuscation de code.
En utilisant une réduction qui est disponible dans 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 __== '__main__':
uncleaned_str = "abc&def#ghi"
cleaned_str = replacer(uncleaned_str, [("&","\&"),("#","\#")])
print(cleaned_str) # "abc\&def\#ghi"
En python2.7, vous n'avez pas besoin d'importer réduire, mais en python3. *, Vous devez l'importer à partir du module functools.
>>> a = '&#'
>>> print a.replace('&', r'\&')
\&#
>>> print a.replace('#', r'\#')
&\#
>>>
Vous voulez utiliser une chaîne 'brute' (indiquée par le préfixe 'r' qui préfixe la chaîne de remplacement), car les chaînes brutes ne traitent pas spécialement la barre oblique inversée.