Cara terbaik untuk mengganti banyak karakter dalam sebuah string?

Jawaban:

432

Mengganti dua karakter

Saya menghitung semua metode dalam jawaban saat ini bersama dengan satu tambahan.

Dengan string masukan dari abc&def#ghidan mengganti & -> \ & dan # -> \ #, cara tercepat adalah mengikat rantai pengganti seperti ini:text.replace('&', '\&').replace('#', '\#') .

Pengaturan waktu untuk setiap fungsi:

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

Berikut fungsinya:

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

Jangka waktu seperti ini:

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

Mengganti 17 karakter

Berikut kode yang mirip untuk melakukan hal yang sama tetapi dengan lebih banyak karakter untuk melarikan diri (\ `* _ {}> # + -.! $):

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

Inilah hasil untuk string input yang sama abc&def#ghi:

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

Dan dengan string input yang lebih panjang ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

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

Menambahkan beberapa varian:

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)

Dengan input yang lebih pendek:

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

Dengan input yang lebih panjang:

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

Jadi saya akan gunakan bauntuk keterbacaan dan kecepatan.

Tambahan

Diminta oleh haccks di komentar, satu perbedaan antara abdan baadalah if c in text:cek. Mari kita uji mereka terhadap dua varian lagi:

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)

Waktu dalam μs per loop pada Python 2.7.14 dan 3.6.3, dan pada mesin yang berbeda dari set sebelumnya, jadi tidak dapat dibandingkan secara langsung.

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

Kita dapat menyimpulkan bahwa:

  • Orang-orang dengan cek hingga 4x lebih cepat daripada mereka yang tidak memeriksa

  • ab_with_checksedikit memimpin pada Python 3, tetapi ba(dengan cek) memiliki keunggulan yang lebih besar pada Python 2

  • Namun, pelajaran terbesar di sini adalah Python 3 hingga 3x lebih cepat dari Python 2 ! Tidak ada perbedaan besar antara yang paling lambat di Python 3 dan tercepat di Python 2!

Hugo
sumber
4
Mengapa ini bukan jawaban yang dikecualikan?
Sup Ayam
Apakah if c in text:perlu masuk ba?
pukul
@haccks Tidak perlu, tapi 2-3x lebih cepat dengannya. Tali pendek, dengan: 1.45 usec per loop, dan tanpa: 5.3 usec per loop, panjang tali, dengan: 4.38 usec per loopdan tanpa: 7.03 usec per loop. (Perhatikan ini tidak langsung sebanding dengan hasil di atas, karena ini adalah mesin yang berbeda dll.)
Hugo
1
@Hugo; Saya pikir perbedaan waktu ini adalah karena replacedipanggil hanya ketika cditemukan textdalam kasus basementara dipanggil dalam setiap iterasi di ab.
pukul
2
@haccks Terima kasih, saya telah memperbarui jawaban saya dengan pengaturan waktu lebih lanjut: menambahkan cek lebih baik untuk keduanya, tetapi pelajaran terbesar adalah Python 3 hingga 3x lebih cepat!
Hugo
73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi
ghostdog74
sumber
Mengapa backslash ganda diperlukan? Mengapa "\" tidak berfungsi?
axolotl
3
Double backslash lolos dari backslash, jika tidak python akan menafsirkan "\" sebagai karakter kutipan literal dalam string yang masih terbuka.
Riet
Mengapa Anda harus melakukannya string=string.replace(ch,"\\"+ch)? Tidak string.replace(ch,"\\"+ch)cukup?
MattSom
1
@MattSom replace () tidak mengubah string asli, tetapi mengembalikan salinan. Jadi, Anda memerlukan penugasan agar kode memiliki efek apa pun.
Ben Brian
3
Apakah Anda benar-benar membutuhkan jika? Sepertinya duplikasi dari apa yang akan dilakukan penggantian itu.
lorenzo
32

Cukup rantai replacefungsi seperti ini

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

Jika penggantiannya akan lebih banyak, Anda dapat melakukan ini dengan cara yang umum ini

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

Berikut adalah metode python3 menggunakan str.translatedan str.maketrans:

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

String yang dicetak adalah abc\&def\#ghi.

tommy.carstensen
sumber
2
Ini adalah jawaban yang baik, tetapi dalam praktiknya melakukan satu .translate()tampaknya lebih lambat dari tiga dirantai .replace()(menggunakan CPython 3.6.4).
Changaco
@ Changaco Terima kasih atas pengaturan waktunya 👍 Dalam praktiknya saya akan menggunakan replace()diri saya sendiri, tetapi saya menambahkan jawaban ini demi kelengkapan.
tommy.carstensen
Untuk string besar dan banyak penggantian ini harus lebih cepat, meskipun beberapa pengujian akan menyenangkan ...
Graipher
Yah, ini bukan di komputer saya (sama untuk penggantian 2 dan 17).
Graipher
bagaimana '\#'valid tidak harus itu r'\#'atau '\\#'? Mungkin masalah pemformatan blok kode.
parity3
16

Apakah Anda akan selalu menambahkan backslash? Jika ya, coba

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

Ini mungkin bukan metode yang paling efisien tapi saya pikir ini yang paling mudah.

kennytm
sumber
15
aarrgghh tryr'\\\1'
John Machin
10

Terlambat ke pesta, tetapi saya kehilangan banyak waktu dengan masalah ini sampai saya menemukan jawaban saya.

Pendek dan manis, translatelebih unggulreplace . Jika Anda lebih tertarik pada fungsi dari waktu ke waktu optimasi, jangan gunakanreplace .

Juga gunakan translatejika Anda tidak tahu apakah set karakter yang akan diganti tumpang tindih set karakter yang digunakan untuk mengganti.

Inti masalah:

Menggunakan replaceAnda akan secara naif mengharapkan cuplikan "1234".replace("1", "2").replace("2", "3").replace("3", "4")untuk kembali "2344", tetapi sebenarnya akan kembali "4444".

Terjemahan tampaknya melakukan apa yang awalnya diinginkan OP.

Sebastialonso
sumber
6

Anda dapat mempertimbangkan menulis fungsi pelarian umum:

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

Dengan cara ini Anda dapat membuat fungsi Anda dapat dikonfigurasi dengan daftar karakter yang harus diloloskan.

Victor Olex
sumber
3

FYI, ini sedikit atau tidak berguna untuk OP tetapi mungkin berguna untuk pembaca lain (tolong jangan downvote, saya tahu ini).

Sebagai latihan yang agak konyol tapi menarik, ingin melihat apakah saya bisa menggunakan pemrograman fungsional python untuk menggantikan beberapa karakter. Saya cukup yakin ini TIDAK mengalahkan hanya memanggil ganti () dua kali. Dan jika kinerja adalah masalah, Anda dapat dengan mudah mengalahkan ini dalam karat, C, julia, perl, java, javascript dan bahkan mungkin awk. Ia menggunakan paket 'pembantu' eksternal yang disebut pytoolz , dipercepat melalui cython ( cytoolz, ini adalah paket 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),))))))

Saya bahkan tidak akan menjelaskan ini karena tidak ada yang mau repot-repot menggunakan ini untuk menyelesaikan banyak penggantian. Namun demikian, saya merasa agak berhasil dalam melakukan ini dan berpikir itu mungkin menginspirasi pembaca lain atau memenangkan kontes kode kebingungan.

paritas3
sumber
1
"pemrograman fungsional" tidak berarti "menggunakan sebanyak mungkin fungsi", Anda tahu.
Craig Andrews
1
Ini adalah pengganti multi-char fungsional yang benar-benar bagus, murni: gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Tidak ada alokasi, tidak ada mutasi, tidak ada efek samping. Dapat dibaca juga.
Craig Andrews
1

Menggunakan mengurangi yang tersedia di python2.7 dan python3. * Anda dapat dengan mudah mengganti substring mutiple dengan cara yang bersih dan pythonic.

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

Dalam python2.7 Anda tidak perlu mengimpor mengurangi tetapi dalam python3. * Anda harus mengimpornya dari modul functools.

CasualCoder3
sumber
1

Mungkin loop sederhana untuk mengganti karakter:

a = '&#'

to_replace = ['&', '#']

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

print(a)

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

Bagaimana dengan ini?

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

kemudian

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

keluaran

\&\#

mirip dengan jawaban

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

Anda ingin menggunakan string 'mentah' (dilambangkan dengan awalan 'r' string pengganti), karena string mentah tidak memperlakukan backslash secara khusus.

jonesy
sumber