วิธีที่ดีที่สุดในการแทนที่อักขระหลายตัวในสตริง?


199

ฉันต้องการแทนที่ตัวละครบางตัวดังนี้: &\&, #\#, ...

ฉันเขียนโค้ดดังนี้ แต่ฉันเดาว่าควรมีวิธีที่ดีกว่า คำใบ้ใด ๆ

strs = strs.replace('&', '\&')
strs = strs.replace('#', '\#')
...

คำตอบ:


434

การแทนที่อักขระสองตัว

ฉันหมดเวลาวิธีการทั้งหมดในคำตอบปัจจุบันพร้อมกับเพิ่มอีกหนึ่งวิธี

ด้วยสตริงใส่ของabc&def#ghiและแทนที่ & -> \ & # และ -> \ # text.replace('&', '\&').replace('#', '\#')วิธีที่เร็วที่สุดคือการเข้าด้วยกันเปลี่ยนเช่นนี้:

การกำหนดเวลาสำหรับแต่ละฟังก์ชั่น:

  • ก) ลูป 1000000 ดีที่สุด 3: 1.47 μsต่อลูป
  • b) ลูป 1000000 ที่ดีที่สุดคือ 3: 1.51 μsต่อลูป
  • c) 100000 ลูปที่ดีที่สุดคือ 3: 12.3 μsต่อลูป
  • d) 100000 ลูปที่ดีที่สุดคือ 3: 12 μsต่อลูป
  • e) 100000 ลูปที่ดีที่สุดคือ 3: 3.27 μsต่อลูป
  • f) ลูป 1000000 ดีที่สุด 3: 0.817 μsต่อลูป
  • g) ลูป 100000 ที่ดีที่สุดคือ 3: 3.64 μsต่อลูป
  • h) ลูป 1000000 ดีที่สุดคือ 3: 0.927 μsต่อลูป
  • i) ลูป 1000000 ดีที่สุดคือ 3: 0.814 μsต่อลูป

นี่คือฟังก์ชั่น:

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

หมดเวลาแบบนี้:

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 ตัวอักษร

นี่คือรหัสที่คล้ายกันที่จะทำเหมือนกัน แต่มีตัวละครให้หนีมากกว่า (\ `* _ {}> # + -.! $):

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

นี่คือผลลัพธ์สำหรับอินพุตสตริงเดียวกันabc&def#ghi:

  • a) 100000 ลูปที่ดีที่สุดคือ 3: 6.72 μsต่อลูป
  • b) 100000 ลูปที่ดีที่สุดคือ 3: 2.64 μsต่อลูป
  • c) 100,000 ลูปที่ดีที่สุดคือ 3: 11.9 μsต่อลูป
  • d) 100000 ลูปที่ดีที่สุดคือ 3: 4.92 μsต่อลูป
  • e) 100000 ลูปที่ดีที่สุดคือ 3: 2.96 μsต่อลูป
  • f) 100000 ลูปที่ดีที่สุดคือ 3: 4.29 μsต่อลูป
  • g) ลูป 100000 ดีที่สุดคือ 3: 4.68 μsต่อลูป
  • h) 100000 ลูปที่ดีที่สุดคือ 3: 4.73 μsต่อลูป
  • i) ลูป 100000 ที่ดีที่สุดคือ 3: 4.24 μsต่อลูป

และด้วยสตริงอินพุตที่ยาวขึ้น ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100000 ลูปที่ดีที่สุดคือ 3: 7.59 μsต่อลูป
  • b) 100000 ลูปที่ดีที่สุดคือ 3: 6.54 μsต่อลูป
  • c) 100000 ลูปที่ดีที่สุดคือ 3: 16.9 μsต่อลูป
  • d) 100000 ลูปที่ดีที่สุดคือ 3: 7.29 μsต่อลูป
  • e) 100000 ลูปที่ดีที่สุดคือ 3: 12.2 μsต่อลูป
  • f) 100,000 ลูปที่ดีที่สุดคือ 3: 5.38 μsต่อลูป
  • g) 10,000 ลูปที่ดีที่สุดคือ 3: 21.7 μsต่อลูป
  • h) 100000 ลูปที่ดีที่สุดคือ 3: 5.7 μsต่อลูป
  • i) 100000 ลูปที่ดีที่สุดคือ 3: 5.13 μsต่อลูป

การเพิ่มความหลากหลาย:

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)

ด้วยอินพุตที่สั้นกว่า:

  • ab) 100,000 ลูปที่ดีที่สุดคือ 3: 7.05 μsต่อลูป
  • ba) 100000 ลูปที่ดีที่สุดคือ 3: 2.4 μsต่อลูป

ด้วยอินพุตที่ยาวขึ้น:

  • ab) 100,000 ลูปที่ดีที่สุดคือ 3: 7.71 μsต่อลูป
  • ba) 100000 ลูปที่ดีที่สุดคือ 3: 6.08 μsต่อลูป

ดังนั้นฉันจะใช้baสำหรับการอ่านและความเร็ว

ภาคผนวก

แจ้งโดย haccks ในความคิดเห็นหนึ่งความแตกต่างระหว่างabและbaคือการif c in text:ตรวจสอบ ลองทดสอบกับอีกสองสายพันธุ์:

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)

เวลาเป็นμsต่อลูปใน Python 2.7.14 และ 3.6.3 และบนเครื่องที่แตกต่างจากชุดก่อนหน้าดังนั้นจึงไม่สามารถเปรียบเทียบได้โดยตรง

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

เราสามารถสรุปได้ว่า:

  • ผู้ที่มีเช็คจะเร็วกว่า 4x ที่ไม่มีเช็ค

  • ab_with_checkอยู่ในกลุ่มลูกค้าเป้าหมายบน Python 3 เล็กน้อย แต่ba(ด้วยเช็ค) มีกลุ่มลูกค้าเป้าหมายมากกว่าในกลุ่ม Python 2

  • อย่างไรก็ตามบทเรียนที่ยิ่งใหญ่ที่สุดของที่นี่คือPython 3 นั้นเร็วกว่า Python 2 ถึง 3 เท่า ! ไม่มีความแตกต่างอย่างมากระหว่าง Python 3 ที่ช้าที่สุดกับ Python 2 ที่เร็วที่สุด!


4
ทำไมนี่ไม่ใช่คำตอบที่ยกเว้น?
ซุปไก่

เป็นif c in text:ในสิ่งที่จำเป็นba?
haccks

@haccks มันไม่จำเป็น แต่มันเร็วกว่าถึง 2-3 เท่า สตริงระยะสั้นด้วย: 1.45 usec per loopและโดย: 5.3 usec per loopสตริงยาวกับ: และไม่มี:4.38 usec per loop 7.03 usec per loop(โปรดทราบว่าสิ่งเหล่านี้ไม่ได้เปรียบเทียบโดยตรงกับผลลัพธ์ข้างต้นเพราะเป็นเครื่องที่แตกต่างกัน ฯลฯ )
Hugo

1
@Hugo; ผมคิดว่าความแตกต่างในเวลานี้เป็นเพราะreplaceถูกเรียกเฉพาะเมื่อcพบในtextในกรณีที่ในขณะที่มันถูกเรียกในทุกย้ำในba ab
haccks

2
@haccks ขอบคุณฉันได้อัปเดตคำตอบของฉันด้วยการกำหนดเวลาเพิ่มเติม: การเพิ่มการตรวจสอบนั้นดีกว่าสำหรับทั้งคู่ แต่บทเรียนที่ยิ่งใหญ่ที่สุดคือ Python 3 เร็วขึ้นถึง 3 เท่า!
Hugo

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

เหตุใดจึงต้องมีแบ็กสแลชสองครั้ง เพราะเหตุใด "\" จึงไม่ทำงาน
axolotl

3
เครื่องหมายแบ็กสแลชคู่จะหนีจากเครื่องหมายแบ็กสแลชมิฉะนั้นไพ ธ อนจะตีความ "\" เป็นอักขระเครื่องหมายคำพูดตามตัวอักษรภายในสตริงที่ยังคงเปิดอยู่
Riet

ทำไมคุณต้องstring=string.replace(ch,"\\"+ch)? ยังไม่string.replace(ch,"\\"+ch)พอหรอก
MattSom

1
@MattSom replace () ไม่ได้แก้ไขสตริงเดิม แต่ส่งคืนสำเนา ดังนั้นคุณต้องกำหนดรหัสเพื่อให้มีผลกระทบใด ๆ
Ben Brian

3
คุณต้องการถ้า ดูเหมือนว่าซ้ำซ้อนกับสิ่งที่ผู้แทนที่จะทำอยู่ดี
Lorenzo

32

เพียงเชื่อมโยงreplaceฟังก์ชั่นเช่นนี้

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

หากการแทนที่มีจำนวนมากขึ้นคุณสามารถทำได้ด้วยวิธีทั่วไป

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

30

นี่คือวิธีการใช้ python3 str.translateและstr.maketrans:

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

abc\&def\#ghiสตริงพิมพ์เป็น


2
นี่เป็นคำตอบที่ดี แต่ในทางปฏิบัติการทำอย่างใดอย่างหนึ่ง.translate()ดูเหมือนจะช้ากว่าการผูกมัดสามครั้ง.replace()(ใช้ CPython 3.6.4)
Changaco

@Changaco ขอบคุณสำหรับเวลา👍ในทางปฏิบัติฉันจะใช้replace()ตัวเอง แต่ฉันได้เพิ่มคำตอบนี้เพื่อความสมบูรณ์
tommy.carstensen

สำหรับสตริงขนาดใหญ่และการแทนที่จำนวนมากสิ่งนี้น่าจะเร็วกว่าแม้ว่าการทดสอบบางอย่างจะดี ...
Graipher

มันไม่ได้อยู่ในเครื่องของฉัน (เหมือนกันสำหรับการแทนที่ 2 และ 17)
Graipher

วิธีการที่'\#'ถูกต้อง? ไม่ควรจะเป็นr'\#'หรือ'\\#'? อาจเป็นปัญหาการจัดรูปแบบการบล็อกรหัสบางที
parity3

16

คุณจะใส่แบ็กสแลชไว้ข้างหน้าเสมอหรือไม่ ถ้าเป็นเช่นนั้นลอง

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

มันอาจไม่ใช่วิธีที่มีประสิทธิภาพที่สุด แต่ฉันคิดว่ามันเป็นวิธีที่ง่ายที่สุด


15
aarrgghh tryr'\\\1'
John Machin

10

ไปงานปาร์ตี้สาย แต่ฉันเสียเวลามากกับปัญหานี้จนกระทั่งฉันพบคำตอบของฉัน

สั้นและหวานtranslatereplaceจะดีกว่า หากคุณสนใจฟังก์ชั่นการใช้เวลามากขึ้นอย่าใช้replaceเวลาไม่ได้ใช้

นอกจากนี้ยังใช้translateถ้าคุณไม่ทราบว่าชุดของตัวละครที่จะถูกแทนที่ทับซ้อนกับชุดของตัวละครที่ใช้ในการแทนที่

กรณีในจุด:

การใช้replaceอย่างไร้เดียงสาคุณคาดหวังว่าข้อมูลโค้ด"1234".replace("1", "2").replace("2", "3").replace("3", "4")จะส่งคืน"2344"แต่จริง"4444"ๆ แล้วจะกลับมา

ดูเหมือนว่าการแปลจะทำงานตามที่ OP ต้องการ


6

คุณอาจลองเขียนฟังก์ชัน escape ทั่วไป:

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

วิธีนี้คุณสามารถทำให้ฟังก์ชันของคุณสามารถกำหนดค่าได้ด้วยรายการอักขระที่ควรหลีกเลี่ยง


3

FYI นี่เป็นเพียงเล็กน้อยหรือไม่มีประโยชน์กับ OP แต่อาจใช้กับผู้อ่านคนอื่น ๆ (โปรดอย่าลงคะแนนฉันรู้เรื่องนี้)

เป็นการออกกำลังกายที่ค่อนข้างไร้สาระ แต่น่าสนใจฉันต้องการดูว่าฉันสามารถใช้การเขียนโปรแกรมฟังก์ชั่นของหลามเพื่อแทนที่ตัวอักษรหลายตัวได้หรือไม่ ฉันค่อนข้างแน่ใจว่าสิ่งนี้จะไม่ชนะเพียงแค่เรียกแทนที่ () สองครั้ง และหากประสิทธิภาพเป็นปัญหาคุณสามารถเอาชนะสิ่งนี้ได้อย่างง่ายดายในสนิม, C, julia, perl, java, javascript และอาจจะแปลกใจ ใช้แพ็คเกจ 'ผู้ช่วยเหลือ' ภายนอกที่เรียกว่าpytoolzเร่งผ่าน cython ( cytoolz เป็นแพคเกจ 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),))))))

ฉันจะไม่อธิบายด้วยซ้ำเพราะไม่มีใครสนใจใช้สิ่งนี้เพื่อให้ได้มาแทนที่หลายคน อย่างไรก็ตามฉันรู้สึกว่าประสบความสำเร็จในการทำสิ่งนี้และคิดว่ามันอาจเป็นแรงบันดาลใจให้ผู้อ่านรายอื่นหรือชนะการประกวดรหัสที่ทำให้งงงวย


1
"การโปรแกรมเชิงฟังก์ชัน" ไม่ได้แปลว่า "ใช้ฟังก์ชั่นได้มากเท่าที่จะเป็นไปได้" คุณก็รู้
Craig Andrews

1
นี่คือตัวจำลองแบบมัลติชาร์ที่ทำงานได้ดีและบริสุทธิ์อย่างสมบูรณ์: gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e ไม่มีการจัดสรรไม่มีการกลายพันธุ์ไม่มีผลข้างเคียง อ่านได้เช่นกัน
Craig Andrews

1

การใช้การลดซึ่งมีอยู่ใน python2.7 และ python3. * คุณสามารถแทนที่สตริงย่อยหลายอันได้อย่างง่ายดายและสะอาด

# 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 คุณไม่จำเป็นต้องนำเข้าการลดลง แต่ใน python3 * คุณต้องนำเข้าจากโมดูล functools



1

แล้วเรื่องนี้ล่ะ

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

แล้วก็

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

เอาท์พุต

\&\#

คล้ายกับคำตอบ


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

คุณต้องการใช้สตริง 'raw' (แทนด้วย 'r' นำหน้าสตริงการแทนที่) เนื่องจากสตริง raw จะไม่ใช้ backslash เป็นพิเศษ


0

วิธีขั้นสูงโดยใช้ regex

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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.