Bir dizedeki birden çok karakteri değiştirmenin en iyi yolu?


Yanıtlar:


472

İki karakterin değiştirilmesi

Mevcut cevaplardaki tüm yöntemleri bir ekstra ile birlikte zamanladım.

Bir giriş dizesi ile abc&def#ghive değiştirme & -> \ & ve # -> \ #, en hızlı şekilde bir araya zincirine böyle değiştirmeler oldu: text.replace('&', '\&').replace('#', '\#').

Her işlev için zamanlamalar:

  • a) 1000000 döngü, döngü başına en iyi 3: 1.47 μs
  • b) 1000000 döngü, döngü başına en iyi 3: 1,51 μs
  • c) 100000 döngü, döngü başına en iyi 3: 12,3 μs
  • d) 100000 döngü, döngü başına en iyi 3: 12 μs
  • e) 100000 döngü, döngü başına en iyi 3: 3.27 μs
  • f) 1000000 döngü, döngü başına en iyi 3: 0,817 μs
  • g) 100000 döngü, döngü başına en iyi 3: 3.64 μs
  • h) 1000000 döngü, döngü başına en iyisi 3: 0.927 μs
  • i) 1000000 döngü, döngü başına en iyisi 3: 0.814 μs

İşte fonksiyonlar:

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

Şu şekilde zamanlanmış:

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 karakter yerine

İşte aynısını yapmak için, ancak daha fazla karakterden çıkılacak benzer kod (\ `* _ {}> # + -.! $):

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

İşte aynı girdi dizesi için sonuçlar abc&def#ghi:

  • a) 100000 döngü, döngü başına en iyi 3: 6.72 μs
  • b) 100000 döngü, döngü başına en iyi 3: 2,64 μs
  • c) 100000 döngü, döngü başına en iyi 3: 11,9 μs
  • d) 100000 döngü, döngü başına en iyi 3: 4.92 μs
  • e) 100000 döngü, döngü başına en iyi 3: 2,96 μs
  • f) 100000 döngü, döngü başına en iyi 3: 4.29 μs
  • g) 100000 döngü, döngü başına en iyi 3: 4.68 μs
  • h) 100000 döngü, döngü başına en iyi 3: 4.73 μs
  • i) 100000 döngü, döngü başına en iyi 3: 4.24 μs

Ve daha uzun bir girdi dizesiyle ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100000 döngü, döngü başına en iyi 3: 7,59 μs
  • b) 100000 döngü, döngü başına en iyi 3: 6,54 μs
  • c) 100000 döngü, döngü başına en iyi 3: 16,9 μs
  • d) 100000 döngü, döngü başına en iyi 3: 7,29 μs
  • e) 100000 döngü, döngü başına en iyi 3: 12,2 μs
  • f) 100000 döngü, döngü başına en iyi 3: 5,38 μs
  • g) 10000 döngü, döngü başına en iyi 3: 21,7 μs
  • h) 100000 döngü, döngü başına en iyi 3: 5,7 μs
  • i) 100000 döngü, döngü başına en iyi 3: 5,13 μs

Birkaç varyant eklemek:

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)

Daha kısa girişle:

  • ab) 100000 döngü, döngü başına en iyi 3: 7.05 μs
  • ba) 100000 döngü, döngü başına en iyi 3: 2,4 μs

Daha uzun girdi ile:

  • ab) 100000 döngü, döngü başına en iyi 3: 7.71 μs
  • ba) 100000 döngü, döngü başına en iyi 3: 6,08 μs

Bu yüzden baokunabilirlik ve hız için kullanacağım .

Ek

Arasında bir fark, yorumlardaki haccks tarafından istendiğinde abve babir if c in text:çek. Onları iki değişkenle daha test edelim:

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)

Python 2.7.14 ve 3.6.3'te ve önceki setten farklı bir makinede döngü başına μs cinsinden süre, bu nedenle doğrudan karşılaştırılamaz.

╭────────────╥──────┬───────────────┬──────┬──────────────────╮
│ Py, input  ║  ab  │ ab_with_check │  ba  │ ba_without_check │
╞════════════╬══════╪═══════════════╪══════╪══════════════════╡
│ Py2, short ║ 8.814.223.458.01          │
│ Py3, short ║ 5.541.341.465.34          │
├────────────╫──────┼───────────────┼──────┼──────────────────┤
│ Py2, long  ║ 9.37.156.858.55          │
│ Py3, long  ║ 7.434.384.417.02          │
└────────────╨──────┴───────────────┴──────┴──────────────────┘

Bunu sonuçlandırabiliriz:

  • Çekli olanlar, çek olmayanlara göre 4 kata kadar daha hızlıdır

  • ab_with_checkPython 3'te biraz önde, ancak ba(kontrolle) Python 2'de daha fazla öne çıkıyor

  • Bununla birlikte, buradaki en büyük ders, Python 3'ün Python 2'den 3 kata kadar daha hızlı olmasıdır ! Python 3'te en yavaş ile Python 2'de en hızlı arasında çok büyük bir fark yoktur!


8
Neden bu istisnai cevap değil?
Tavuk Suop

İçinde if c in text:gerekli bami?
haccks

1
@Hugo; Zamanında bu fark nedeniyle olduğunu düşünüyorum replaceyalnızca denir cbulunursa textdurumunda babunun her tekrarında denirken ab.
haccks

2
@haccks Teşekkürler, cevabımı daha fazla zamanlama ile güncelledim: çek eklemek her ikisi için de daha iyidir, ancak en önemli ders Python 3'ün 3 kata kadar daha hızlı olmasıdır!
Hugo


73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi

Neden çift ters eğik çizgi gerekliydi? Neden sadece "\" çalışmıyor?
axolotl

3
Çift ters eğik çizgi, ters eğik çizgiden kaçar, aksi takdirde python, "\" yi hala açık bir dizge içindeki değişmez bir tırnak karakteri olarak yorumlar.
Riet

Neden buna ihtiyacın var string=string.replace(ch,"\\"+ch)? Sadece mi string.replace(ch,"\\"+ch)yeterince?
MattSom

1
@MattSom replace () orijinal dizeyi değiştirmez, ancak bir kopya döndürür. Bu nedenle, kodun herhangi bir etkiye sahip olması için atamaya ihtiyacınız var.
Ben Brian

4
Eğer gerçekten ihtiyacınız var mı? Değiştirmenin yine de yapacağı şeyin bir kopyası gibi görünüyor.
lorenzo

37

İşte str.translateve kullanan bir python3 yöntemi str.maketrans:

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

Yazdırılan dize abc\&def\#ghi.


2
Bu iyi bir cevap, ancak pratikte bir tane yapmak .translate()üç zincirden daha yavaş görünüyor .replace()(CPython 3.6.4 kullanarak).
Changaco

@Changaco Zamanlama için teşekkürler 👍 Pratikte replace()kendimi kullanırdım ama bu cevabı bütünlük adına ekledim.
tommy.carstensen

Büyük dizeler ve birçok değiştirme için bu daha hızlı olmalı, ancak bazı testler iyi olabilir ...
Graipher

Benim makinemde değil (2 ve 17 değiştirme için aynı).
Graipher

1
Bu yöntem, zincirleme sürümlerin yapmadığı "bozucu değiştirmelerin" gerçekleştirilmesine izin verir. Örneğin, "a" yı "b" ile ve "b" yi "a" ile değiştirin.
adavid

34

Bunun replacegibi işlevleri basitçe zincirleyin

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

Değişimlerin sayısı daha fazla olacaksa, bunu bu genel şekilde yapabilirsiniz.

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

16

Her zaman bir ters eğik çizgi mi ekleyeceksiniz? Eğer öyleyse, dene

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

En verimli yöntem olmayabilir ama bence en kolayı bu.


15
aarrgghh tryr'\\\1'
John Machin

11

Partiye geç kaldım ama cevabımı bulana kadar bu sorunla çok zaman kaybettim.

Kısa ve tatlıdır, translateüstündürreplace . Zaman optimizasyonu içinde işlevsellikle daha fazla ilgileniyorsanız, kullanmayın replace.

Ayrıca translate, değiştirilecek karakter kümesinin değiştirmek için kullanılan karakter kümesiyle örtüşüp örtüşmediğini bilmiyorsanız kullanın.

Konuşma konusu olan mesele:

replaceSizi kullanmak saf bir şekilde pasajın "1234".replace("1", "2").replace("2", "3").replace("3", "4")geri dönmesini beklersiniz "2344", ancak aslında geri dönecektir "4444".

Çeviri, başlangıçta istenen OP'yi gerçekleştiriyor gibi görünüyor.


6

Genel bir kaçış işlevi yazmayı düşünebilirsiniz:

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

Bu şekilde, işlevinizi öncelenmesi gereken bir karakter listesi ile yapılandırılabilir hale getirebilirsiniz.


3

Bilginize, bu OP için çok az faydalıdır veya hiç faydası yoktur, ancak diğer okuyucular için yararlı olabilir (lütfen olumsuz oy vermeyin, bunun farkındayım).

Biraz saçma ama ilginç bir alıştırma olarak, birden çok karakteri değiştirmek için python işlevsel programlamayı kullanıp kullanamayacağımı görmek istedim. Bunun sadece replace () çağrısını iki kez geçmediğinden eminim. Ve performans bir sorun olsaydı, bunu rust, C, julia, perl, java, javascript ve hatta belki awk ile kolayca yenebilirdiniz. Pytoolz adı verilen ve cython ( cytoolz, bu bir pypi paketi ) ile hızlandırılan harici bir 'yardımcılar' paketi kullanır .

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

Bunu açıklamayacağım bile çünkü kimse bunu birden fazla değiştirme yapmak için kullanmaya zahmet etmeyecek. Yine de, bunu yaparken bir şekilde başarılı olduğumu hissettim ve diğer okuyuculara ilham verebileceğini veya bir kod gizleme yarışmasını kazanabileceğini düşündüm.


1
"işlevsel programlama", "mümkün olduğu kadar çok işlev kullanmak" anlamına gelmez, biliyorsunuz.
Craig Andrews

1
Bu, mükemmel derecede iyi, saf, işlevsel bir çok karakterli ikame maddesidir : gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Tahsis yok, mutasyon yok, yan etki yok. Okunabilir de.
Craig Andrews

2

Python2.7 ve python3. * 'Te bulunan indirgeme özelliğini kullanarak çok sayıda alt dizeyi temiz ve pitonik bir şekilde kolayca değiştirebilirsiniz.

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

Python2.7'de indirgeme aktarmanız gerekmez, ancak python3. * 'De onu functools modülünden içe aktarmanız gerekir.


'Eğer' koşulunu eklemek için ( baHugo'nun bahsettiği değişken ):lambda text, ptuple: text.replace(ptuple[0], ptuple[1]) if ptuple[0] in text else text
Jean Monet

1

Belki karakterlerin yerini alacak basit bir döngü:

a = '&#'

to_replace = ['&', '#']

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

print(a)

>>> \&\#

1

Buna ne dersin?

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

sonra

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

çıktı

\&\#

cevaba benzer


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

Ham dizeler ters eğik çizgiyi özel olarak ele almadığından, bir 'ham' dizge kullanmak istiyorsunuz (yedek dizenin öneki 'r' ile gösterilir).


0

normal ifade kullanarak gelişmiş yol

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)

0

Python 3.8 ve üzeri için atama ifadeleri kullanılabilir

(text := text.replace(s, f"\\{i}") for s in "&#" if s in text)

Bununla birlikte, bunun PEP 572'de açıklandığı gibi atama ifadelerinin "uygun kullanımı" olarak kabul edilip edilmeyeceğinden emin değilim , ancak temiz görünüyor ve oldukça iyi okuyor (gözlerime). Tüm ara dizeleri de istiyorsanız bu "uygun" olacaktır. Örneğin, (tüm küçük harfli ünlüleri kaldırarak):

text = "Lorem ipsum dolor sit amet"
intermediates = [text := text.replace(i, "") for i in "aeiou" if i in text]

['Lorem ipsum dolor sit met',
 'Lorm ipsum dolor sit mt',
 'Lorm psum dolor st mt',
 'Lrm psum dlr st mt',
 'Lrm psm dlr st mt']

Artı tarafta, kabul edilen cevapta daha hızlı yöntemlerden bazılarından daha hızlı (beklenmedik bir şekilde?) Görünüyor ve hem artan dizi uzunluğu hem de artan sayıda ikame ile güzel bir şekilde çalışıyor gibi görünüyor.

Karşılaştırma

Yukarıdaki karşılaştırmanın kodu aşağıdadır. Hayatımı biraz daha basitleştirmek için rastgele dizeler kullanıyorum ve değiştirilecek karakterler dizenin kendisinden rastgele seçiliyor. (Not: Burada ipython'un% timeit sihrini kullanıyorum, bu yüzden bunu ipython / jupyter'da çalıştırın).

import random, string

def make_txt(length):
    "makes a random string of a given length"
    return "".join(random.choices(string.printable, k=length))

def get_substring(s, num):
    "gets a substring"
    return "".join(random.choices(s, k=num))

def a(text, replace): # one of the better performing approaches from the accepted answer
    for i in replace:
        if i in text:
             text = text.replace(i, "")

def b(text, replace):
    _ = (text := text.replace(i, "") for i in replace if i in text) 


def compare(strlen, replace_length):
    "use ipython / jupyter for the %timeit functionality"

    times_a, times_b = [], []

    for i in range(*strlen):
        el = make_txt(i)
        et = get_substring(el, replace_length)

        res_a = %timeit -n 1000 -o a(el, et) # ipython magic

        el = make_txt(i)
        et = get_substring(el, replace_length)
        
        res_b = %timeit -n 1000 -o b(el, et) # ipython magic

        times_a.append(res_a.average * 1e6)
        times_b.append(res_b.average * 1e6)
        
    return times_a, times_b

#----run
t2 = compare((2*2, 1000, 50), 2)
t10 = compare((2*10, 1000, 50), 10)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.