Najlepszy sposób na zamianę wielu znaków w ciągu?

Odpowiedzi:

434

Zastąpienie dwóch znaków

Zmierzyłem wszystkie metody w bieżących odpowiedziach wraz z jedną dodatkową.

Z ciągiem wejściowym abc&def#ghii zastąpienie & -> \ & i # -> \ #, najszybszym sposobem było łańcucha razem zamienniki tak: text.replace('&', '\&').replace('#', '\#').

Czasy dla każdej funkcji:

  • a) 1000000 pętli, najlepiej 3: 1,47 μs na pętlę
  • b) 1000000 pętli, najlepiej 3: 1,51 μs na pętlę
  • c) 100000 pętli, najlepiej 3: 12,3 μs na pętlę
  • d) 100000 pętli, najlepiej 3: 12 μs na pętlę
  • e) 100000 pętli, najlepiej 3: 3,27 μs na pętlę
  • f) 1000000 pętli, najlepiej 3: 0,817 μs na pętlę
  • g) 100000 pętli, najlepiej 3: 3,64 μs na pętlę
  • h) 1000000 pętli, najlepiej 3: 0,927 μs na pętlę
  • i) 1000000 pętli, najlepiej 3: 0,814 μs na pętlę

Oto funkcje:

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

Czasowo tak:

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

Zamiana 17 znaków

Oto podobny kod, aby zrobić to samo, ale z większą ilością znaków do ucieczki (\ `* _ {}> # + -.! $):

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

Oto wyniki dla tego samego ciągu wejściowego abc&def#ghi:

  • a) 100000 pętli, najlepiej 3: 6,72 μs na pętlę
  • b) 100000 pętli, najlepiej 3: 2,64 μs na pętlę
  • c) 100000 pętli, najlepiej 3: 11,9 μs na pętlę
  • d) 100000 pętli, najlepiej 3: 4,92 μs na pętlę
  • e) 100000 pętli, najlepiej 3: 2,96 μs na pętlę
  • f) 100000 pętli, najlepiej 3: 4,29 μs na pętlę
  • g) 100000 pętli, najlepiej 3: 4,68 μs na pętlę
  • h) 100000 pętli, najlepiej 3: 4,73 μs na pętlę
  • i) 100000 pętli, najlepiej 3: 4,24 μs na pętlę

I przy dłuższym ciągu wejściowym ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100000 pętli, najlepiej 3: 7,59 μs na pętlę
  • b) 100000 pętli, najlepiej 3: 6,54 μs na pętlę
  • c) 100000 pętli, najlepiej 3: 16,9 μs na pętlę
  • d) 100000 pętli, najlepiej 3: 7,29 μs na pętlę
  • e) 100000 pętli, najlepiej 3: 12,2 μs na pętlę
  • f) 100000 pętli, najlepiej 3: 5,38 μs na pętlę
  • g) 10000 pętli, najlepiej 3: 21,7 μs na pętlę
  • h) 100000 pętli, najlepiej 3: 5,7 μs na pętlę
  • i) 100000 pętli, najlepiej 3: 5,13 μs na pętlę

Dodanie kilku wariantów:

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)

Przy krótszym wejściu:

  • ab) 100000 pętli, najlepiej 3: 7,05 μs na pętlę
  • ba) 100000 pętli, najlepiej 3: 2,4 μs na pętlę

Przy dłuższym wejściu:

  • ab) 100000 pętli, najlepiej 3: 7,71 μs na pętlę
  • ba) 100000 pętli, najlepiej 3: 6,08 μs na pętlę

Więc baużyję dla czytelności i szybkości.

Uzupełnienie

Podpowiedzi hacck w komentarzach, jedna różnica między abi bajest if c in text:czek. Przetestujmy je pod kątem jeszcze dwóch wariantów:

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)

Czasy w μs na pętlę w Pythonie 2.7.14 i 3.6.3 i na innym komputerze niż wcześniejszy zestaw, więc nie można bezpośrednio porównać.

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

Możemy stwierdzić, że:

  • Osoby z czekiem są nawet 4x szybsze niż osoby bez czeku

  • ab_with_checkjest nieco na czele w Pythonie 3, ale ba(z czekiem) ma większą przewagę w Pythonie 2

  • Jednak największą lekcją tutaj jest to, że Python 3 jest do 3 razy szybszy niż Python 2 ! Nie ma ogromnej różnicy między najwolniejszym w Pythonie 3 i najszybszym w Pythonie 2!

Hugo
źródło
4
Dlaczego nie jest to wyjątkowa odpowiedź?
Zupa z kurczaka
Czy if c in text:konieczne jest w ba?
haccks,
@haccks Nie jest to konieczne, ale jest 2-3 razy szybsze. Krótki łańcuch, z: 1.45 usec per loopi bez: 5.3 usec per loop, długi ciąg, z: 4.38 usec per loopi bez: 7.03 usec per loop. (Uwaga: nie są one bezpośrednio porównywalne z powyższymi wynikami, ponieważ jest to inna maszyna itp.)
Hugo,
1
@Hugo; Myślę, że ta różnica w czasie wynika z tego, że replacewywoływana jest tylko wtedy, gdy cwystępuje textw przypadku, bagdy jest wywoływana w każdej iteracji w ab.
haccks
2
@haccks Dzięki, zaktualizowałem swoją odpowiedź o dalsze terminy: dodanie sprawdzania jest lepsze dla obu, ale największą lekcją jest to, że Python 3 jest nawet 3 razy szybszy!
Hugo,
73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi
ghostdog74
źródło
Dlaczego potrzebny był podwójny ukośnik odwrotny? Dlaczego po prostu „\” nie działa?
axolotl
3
Podwójny odwrotny ukośnik unika odwrotnego ukośnika, w przeciwnym razie Python interpretowałby „\” jako dosłowny znak cudzysłowu w wciąż otwartym ciągu.
Riet
Dlaczego trzeba string=string.replace(ch,"\\"+ch)? Czy to nie string.replace(ch,"\\"+ch)wystarczy?
MattSom,
1
@MattSom replace () nie modyfikuje oryginalnego ciągu, ale zwraca kopię. Potrzebujesz więc przypisania, aby kod zadziałał.
Ben Brian,
3
Czy naprawdę potrzebujesz tego? Wygląda to na powielanie tego, co i tak zastąpi.
lorenzo
32

Po prostu połącz takie replacefunkcje

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

Jeśli zamienników będzie więcej, możesz to zrobić w ten ogólny sposób

strs, replacements = "abc&def#ghi", {"&": "\&", "#": "\#"}
print "".join([replacements.get(c, c) for c in strs])
# abc\&def\#ghi
thefourtheye
źródło
30

Oto metoda python3 wykorzystująca str.translatei str.maketrans:

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

Wydrukowany ciąg to abc\&def\#ghi.

tommy.carstensen
źródło
2
To dobra odpowiedź, ale w praktyce robienie jednego .translate()wydaje się być wolniejsze niż trzy powiązane .replace()(używając CPython 3.6.4).
Changaco,
@Changaco Dzięki za czas 👍 W praktyce wykorzystałbym replace()siebie, ale dodałem tę odpowiedź ze względu na kompletność.
tommy.carstensen
W przypadku dużych ciągów i wielu zamienników powinno to być szybsze, choć niektóre testy byłyby fajne ...
Graipher
Cóż, nie ma go na moim komputerze (to samo dla zamienników 2 i 17).
Graipher
jak '\#'ważne Nie powinno to być r'\#'albo '\\#'? Może to być problem z formatowaniem bloku kodu.
parytet 3
16

Czy zawsze zamierzasz wstawić ukośnik odwrotny? Jeśli tak, spróbuj

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

Może nie jest to najskuteczniejsza metoda, ale myślę, że jest najłatwiejsza.

kennytm
źródło
15
aarrgghh tryr'\\\1'
John Machin
10

Późno na imprezę, ale straciłem dużo czasu z tym problemem, dopóki nie znalazłem odpowiedzi.

Krótki i słodki, translatejest lepszy odreplace . Jeśli bardziej interesuje Cię funkcjonalność w czasie, nie używaj replace.

Użyj również, translatejeśli nie wiesz, czy zestaw znaków do zastąpienia nakłada się na zestaw znaków używanych do zamiany.

Przykładem:

Korzystając z replaceCiebie, naiwnie oczekujesz, że fragment kodu "1234".replace("1", "2").replace("2", "3").replace("3", "4")wróci "2344", ale w rzeczywistości wróci "4444".

Tłumaczenie wydaje się wykonywać to, czego pierwotnie chciał OP.

Sebastialonso
źródło
6

Możesz rozważyć napisanie ogólnej funkcji zmiany znaczenia:

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

W ten sposób możesz skonfigurować swoją funkcję za pomocą listy znaków, które należy uciec.

Victor Olex
źródło
3

Do twojej wiadomości, jest to mało przydatne lub nieprzydatne dla OP, ale może być przydatne dla innych czytelników (proszę nie głosować, jestem tego świadomy).

Jako nieco niedorzeczne, ale interesujące ćwiczenie, chciałem sprawdzić, czy mogę użyć programowania funkcjonalnego Pythona do zastąpienia wielu znaków. Jestem prawie pewien, że to nie bije tylko wywołania metody replace () dwa razy. A jeśli wydajność była problemem, możesz łatwo pokonać to w rdzeniu, C, Julii, Perlu, Javie, javascript, a może nawet awk. Korzysta z zewnętrznego pakietu „pomocników” o nazwie pytoolz , przyspieszanego przez cython ( cytoolz, jest to pakiet 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),))))))

Nie zamierzam nawet tego wyjaśniać, ponieważ nikt nie zawracałby sobie głowy użyciem tego do wielokrotnego zastąpienia. Mimo to czułem, że jestem w tym pewien, że mogłem zainspirować innych czytelników lub wygrać konkurs zaciemniania kodu.

parzystość 3
źródło
1
„programowanie funkcjonalne” nie oznacza „korzystania z jak największej liczby funkcji”.
Craig Andrews,
1
Jest to doskonale dobry, czysty funkcjonalny zamiennik wielu znaków : gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Bez przydziałów, bez mutacji, bez skutków ubocznych. Czytelny też.
Craig Andrews,
1

Używając funkcji zmniejsz, która jest dostępna w python2.7 i python3. *, Możesz łatwo zastąpić wiele podciągów w czysty i pythoniczny sposób.

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

W python2.7 nie musisz importować redukcji, ale w python3. * Musisz zaimportować go z modułu funkools.

CasualCoder3
źródło
1

Może prosta pętla do zastąpienia znaków:

a = '&#'

to_replace = ['&', '#']

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

print(a)

>>> \&\#
Tiago Wutzke de Oliveira
źródło
1

Co powiesz na to?

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

następnie

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

wynik

\&\#

podobny do odpowiedzi

żydowski mojżesz
źródło
0
>>> a = '&#'
>>> print a.replace('&', r'\&')
\&#
>>> print a.replace('#', r'\#')
&\#
>>> 

Chcesz użyć „nieprzetworzonego” ciągu (oznaczonego przedrostkiem „r” przed zastępującym ciągiem), ponieważ nieprzetworzone ciągi nie będą specjalnie traktować odwrotnego ukośnika.

Jones
źródło
0

zaawansowany sposób za pomocą wyrażenia regularnego

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)
Ahmed4end
źródło