Der beste Weg, um mehrere Zeichen in einer Zeichenfolge zu ersetzen?

Antworten:

432

Zwei Zeichen ersetzen

Ich habe alle Methoden in den aktuellen Antworten zusammen mit einer zusätzlichen zeitlich festgelegt.

Mit einer Eingabezeichenfolge von abc&def#ghiund Ersetzen von & -> \ & und # -> \ # war der schnellste Weg, die Ersetzungen wie folgt zu verketten : text.replace('&', '\&').replace('#', '\#').

Timings für jede Funktion:

  • a) 1000000 Schleifen, am besten 3: 1,47 μs pro Schleife
  • b) 1000000 Schleifen, am besten 3: 1,51 μs pro Schleife
  • c) 100000 Schleifen, am besten 3: 12,3 μs pro Schleife
  • d) 100000 Schleifen, am besten 3: 12 μs pro Schleife
  • e) 100000 Schleifen, am besten 3: 3,27 μs pro Schleife
  • f) 1000000 Schleifen, am besten 3: 0,817 μs pro Schleife
  • g) 100000 Schleifen, am besten 3: 3,64 μs pro Schleife
  • h) 1000000 Schleifen, am besten 3: 0,927 μs pro Schleife
  • i) 1000000 Schleifen, am besten 3: 0,814 μs pro Schleife

Hier sind die Funktionen:

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

Zeitlich wie folgt:

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

17 Zeichen ersetzen

Hier ist ein ähnlicher Code, um dasselbe zu tun, aber mit mehr zu entkommenden Zeichen (\ `* _ {}> # + -.! $):

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

Hier sind die Ergebnisse für dieselbe Eingabezeichenfolge abc&def#ghi:

  • a) 100000 Schleifen, am besten 3: 6,72 μs pro Schleife
  • b) 100000 Schleifen, am besten 3: 2,64 μs pro Schleife
  • c) 100000 Schleifen, am besten 3: 11,9 μs pro Schleife
  • d) 100000 Schleifen, am besten 3: 4,92 μs pro Schleife
  • e) 100000 Schleifen, am besten 3: 2,96 μs pro Schleife
  • f) 100000 Schleifen, am besten 3: 4,29 μs pro Schleife
  • g) 100000 Schleifen, am besten 3: 4,68 μs pro Schleife
  • h) 100000 Schleifen, am besten 3: 4,73 μs pro Schleife
  • i) 100000 Schleifen, am besten 3: 4,24 μs pro Schleife

Und mit einer längeren Eingabezeichenfolge ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100000 Schleifen, am besten 3: 7,59 μs pro Schleife
  • b) 100000 Schleifen, am besten 3: 6,54 μs pro Schleife
  • c) 100000 Schleifen, am besten 3: 16,9 μs pro Schleife
  • d) 100000 Schleifen, am besten 3: 7,29 μs pro Schleife
  • e) 100000 Schleifen, am besten 3: 12,2 μs pro Schleife
  • f) 100000 Schleifen, am besten 3: 5,38 μs pro Schleife
  • g) 10000 Schleifen, am besten 3: 21,7 μs pro Schleife
  • h) 100000 Schleifen, am besten 3: 5,7 μs pro Schleife
  • i) 100000 Schleifen, am besten 3: 5,13 μs pro Schleife

Hinzufügen einiger Varianten:

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)

Mit der kürzeren Eingabe:

  • ab) 100000 Schleifen, am besten 3: 7,05 μs pro Schleife
  • ba) 100000 Schleifen, am besten 3: 2,4 μs pro Schleife

Mit der längeren Eingabe:

  • ab) 100000 Schleifen, am besten 3: 7,71 μs pro Schleife
  • ba) 100000 Schleifen, am besten 3: 6,08 μs pro Schleife

Also werde ich bafür Lesbarkeit und Geschwindigkeit verwenden.

Nachtrag

Aufgefordert durch Haccks in den Kommentaren ist ein Unterschied zwischen abund badie if c in text:Prüfung. Testen wir sie gegen zwei weitere Varianten:

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)

Zeiten in μs pro Schleife unter Python 2.7.14 und 3.6.3 sowie auf einem anderen Computer als dem vorherigen Satz können daher nicht direkt verglichen werden.

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

Können wir schließen, dass:

  • Diejenigen mit dem Scheck sind bis zu 4x schneller als diejenigen ohne den Scheck

  • ab_with_checkliegt bei Python 3 leicht an der Spitze, hat aber ba(mit Häkchen) bei Python 2 einen größeren Vorsprung

  • Die größte Lektion hier ist jedoch, dass Python 3 bis zu dreimal schneller ist als Python 2 ! Es gibt keinen großen Unterschied zwischen dem langsamsten in Python 3 und dem schnellsten in Python 2!

Hugo
quelle
4
Warum ist das nicht die ausgenommene Antwort?
Hühnersuppe
Ist in if c in text:notwendig ba?
Haccks
@haccks Es ist nicht notwendig, aber es ist 2-3x schneller damit. Kurze Saite, mit: 1.45 usec per loopund ohne : 5.3 usec per loop, Lange Saite, mit: 4.38 usec per loopund ohne : 7.03 usec per loop. (Beachten Sie, dass diese nicht direkt mit den obigen Ergebnissen vergleichbar sind, da es sich um eine andere Maschine usw. handelt.)
Hugo
1
@Hugo; Ich denke, dieser Zeitunterschied ist darauf zurückzuführen, dass replacenur aufgerufen wird, wenn er cgefunden textwird, bawährend er in jeder Iteration in aufgerufen wird ab.
Haccks
2
@haccks Danke, ich habe meine Antwort mit weiteren Timings aktualisiert: Das Hinzufügen des Checks ist für beide besser, aber die größte Lektion ist, dass Python 3 bis zu 3x schneller ist!
Hugo
73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi
Ghostdog74
quelle
Warum wurde ein doppelter Backslash benötigt? Warum funktioniert nicht einfach "\"?
Axolotl
3
Der doppelte Backslash entgeht dem Backslash, andernfalls würde Python "\" als wörtliches Anführungszeichen in einer noch offenen Zeichenfolge interpretieren.
Riet
Warum musst du string=string.replace(ch,"\\"+ch)? Ist das nicht string.replace(ch,"\\"+ch)genug?
MattSom
1
@MattSom replace () ändert die ursprüngliche Zeichenfolge nicht, gibt jedoch eine Kopie zurück. Sie benötigen also die Zuweisung, damit der Code eine Wirkung hat.
Ben Brian
3
Benötigen Sie wirklich das Wenn? Es sieht aus wie eine Verdoppelung dessen, was der Ersatz sowieso tun wird.
Lorenzo
32

Verketten Sie einfach die replaceFunktionen wie folgt

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

Wenn die Anzahl der Ersetzungen größer sein soll, können Sie dies auf diese generische Weise tun

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

Hier ist eine Python3-Methode mit str.translateund str.maketrans:

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

Die gedruckte Zeichenfolge ist abc\&def\#ghi.

tommy.carstensen
quelle
2
Dies ist eine gute Antwort, aber in der Praxis .translate()scheint eine langsamer zu sein als drei verkettete .replace()(mit CPython 3.6.4).
Changaco
@Changaco Vielen Dank für das Timing 👍 In der Praxis würde replace()ich mich selbst verwenden, aber ich habe diese Antwort der Vollständigkeit halber hinzugefügt.
Tommy.carstensen
Für große Saiten und viele Ersetzungen sollte dies schneller sein, obwohl einige Tests schön wären ...
Graipher
Nun, es ist nicht auf meiner Maschine (das gleiche gilt für 2 und 17 Ersatz).
Graipher
Wie ist '\#'gültig? sollte es nicht sein r'\#'oder '\\#'? Könnte möglicherweise ein Problem bei der Formatierung von Codeblöcken sein.
Parität3
16

Wirst du immer einen Backslash voranstellen? Wenn ja, versuchen Sie es

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

Es ist vielleicht nicht die effizienteste Methode, aber ich denke, es ist die einfachste.

kennytm
quelle
15
aarrgghh versuchenr'\\\1'
John Machin
10

Spät zur Party, aber ich habe viel Zeit mit diesem Thema verloren, bis ich meine Antwort gefunden habe.

Kurz und bündig, translateist überlegenreplace . Wenn Sie mehr an der Optimierung der Funktionalität im Laufe der Zeit interessiert sind, verwenden Sie diese nicht replace.

Verwenden translateSie diese Option auch, wenn Sie nicht wissen, ob der zu ersetzende Zeichensatz den zum Ersetzen verwendeten Zeichensatz überlappt.

Ein typisches Beispiel:

Wenn replaceSie verwenden, würden Sie naiv erwarten, dass das Snippet "1234".replace("1", "2").replace("2", "3").replace("3", "4")zurückkehrt "2344", aber es wird tatsächlich zurückkehren "4444".

Die Übersetzung scheint das zu leisten, was OP ursprünglich gewünscht hatte.

Sebastialonso
quelle
6

Sie können eine generische Escape-Funktion schreiben:

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

Auf diese Weise können Sie Ihre Funktion mit einer Liste von Zeichen konfigurieren, die maskiert werden sollen.

Victor Olex
quelle
3

Zu Ihrer Information, dies ist für das OP von geringem oder keinem Nutzen, kann aber für andere Leser von Nutzen sein (bitte stimmen Sie nicht ab, ich bin mir dessen bewusst).

Als etwas lächerliche, aber interessante Übung wollte ich sehen, ob ich Python-Funktionsprogrammierung verwenden kann, um mehrere Zeichen zu ersetzen. Ich bin mir ziemlich sicher, dass dies NICHT besser ist, als zweimal replace () aufzurufen. Und wenn Leistung ein Problem wäre, könnten Sie dies leicht in Rost, C, Julia, Perl, Java, Javascript und vielleicht sogar awk schlagen. Es verwendet ein externes ' Helfer' -Paket namens Pytoolz , das über Cython beschleunigt wird ( Cytoolz, es ist ein Pypi-Paket ).

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),))))))

Ich werde dies nicht einmal erklären, da sich niemand die Mühe machen würde, dies zu verwenden, um mehrere Ersetzungen durchzuführen. Trotzdem fühlte ich mich dabei etwas erfolgreich und dachte, es könnte andere Leser inspirieren oder einen Code-Verschleierungswettbewerb gewinnen.

Parität3
quelle
1
"funktionale Programmierung" bedeutet nicht "so viele Funktionen wie möglich verwenden", wissen Sie.
Craig Andrews
1
Dies ist ein perfekter, rein funktioneller Multi-Char-Ersatz: gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Keine Zuordnungen, keine Mutationen, keine Nebenwirkungen. Auch lesbar.
Craig Andrews
1

Mit reduct, das in Python2.7 und Python3 verfügbar ist. * Sie können problemlos mehrere Teilzeichenfolgen auf saubere und pythonische Weise ersetzen.

# 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 müssen Sie Reduce nicht importieren, aber in Python3. * Müssen Sie es aus dem functools-Modul importieren.

CasualCoder3
quelle
1

Vielleicht eine einfache Schleife, die Zeichen ersetzen sollen:

a = '&#'

to_replace = ['&', '#']

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

print(a)

>>> \&\#
Tiago Wutzke de Oliveira
quelle
1

Wie wäre es damit?

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

dann

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

Ausgabe

\&\#

ähnlich zu beantworten

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

Sie möchten eine 'rohe' Zeichenfolge verwenden (gekennzeichnet durch das 'r', das der Ersatzzeichenfolge vorangestellt ist), da rohe Zeichenfolgen den Backslash nicht speziell behandeln sollen.

jonesy
quelle