วิธีการแทนที่สตริงย่อยหลายสตริง?


284

ฉันต้องการใช้ฟังก์ชัน. แทนที่เพื่อแทนที่สตริงจำนวนมาก

ฉันมี

string.replace("condition1", "")

แต่ต้องการมีสิ่งที่ชอบ

string.replace("condition1", "").replace("condition2", "text")

แม้ว่าจะไม่รู้สึกว่าไวยากรณ์ดี

วิธีที่เหมาะสมในการทำเช่นนี้คืออะไร? ชนิดของ grep / regex ที่คุณสามารถทำได้\1และ\2แทนที่ฟิลด์เป็นสตริงการค้นหาที่ต้องการ


7
คุณลองใช้วิธีแก้ปัญหาทั้งหมดที่มีให้หรือไม่? อันไหนเร็วกว่ากัน?
tommy.carstensen

ฉันใช้เวลาในการทดสอบคำตอบทั้งหมดในสถานการณ์ที่แตกต่างกัน ดูstackoverflow.com/questions/59072514/…
Pablo

1
สุจริตฉันชอบวิธีการที่ถูกล่ามโซ่ของคุณกับคนอื่น ๆ ทั้งหมด ฉันลงจอดที่นี่ในขณะที่กำลังมองหาวิธีแก้ปัญหาและใช้ของคุณและมันใช้ได้ดี
frakman1

@ frakman1 +1 ไม่มีเงื่อนงำว่าทำไมสิ่งนี้จึงไม่ถูกเพิ่มขึ้นอีก วิธีอื่นทั้งหมดทำให้การอ่านโค้ดยากขึ้น หากมีฟังก์ชั่นการส่งผ่านอาร์เรย์เพื่อแทนที่สิ่งนี้จะทำงานได้ แต่วิธีการที่ถูกล่ามโซ่ของคุณนั้นชัดเจนที่สุด (อย่างน้อยก็มีจำนวนการเปลี่ยน)
IceFire

คำตอบ:


269

นี่เป็นตัวอย่างสั้น ๆ ที่ควรทำเคล็ดลับด้วยนิพจน์ทั่วไป:

import re

rep = {"condition1": "", "condition2": "text"} # define desired replacements here

# use these three lines to do the replacement
rep = dict((re.escape(k), v) for k, v in rep.iteritems()) 
#Python 3 renamed dict.iteritems to dict.items so use rep.items() for latest versions
pattern = re.compile("|".join(rep.keys()))
text = pattern.sub(lambda m: rep[re.escape(m.group(0))], text)

ตัวอย่างเช่น:

>>> pattern.sub(lambda m: rep[re.escape(m.group(0))], "(condition1) and --condition2--")
'() and --text--'

7
การแทนที่เกิดขึ้นในรอบเดียว
Andrew Clark

26
dkamins: มันไม่ฉลาดเกินไป, มันไม่ฉลาดเท่าที่ควร (เราควรปลดล็อกกุญแจก่อนที่จะเข้าร่วมกับ "|") ทำไมไม่ overengineered เพราะวิธีนี้เราจะทำมันในหนึ่งผ่าน (= เร็ว) และเราจะทำแทนทั้งหมดในเวลาเดียวกันหลีกเลี่ยงการปะทะกันเหมือน"spamham sha".replace("spam", "eggs").replace("sha","md5")เป็น"eggmd5m md5"แทน"eggsham md5"
บินแกะ

8
@ AndrewClark ฉันจะขอบคุณมากถ้าคุณสามารถอธิบายสิ่งที่เกิดขึ้นในบรรทัดสุดท้ายด้วยแลมบ์ดา
แร่ธาตุ

11
สวัสดีฉันสร้างกระทู้เล็ก ๆ ขึ้นมาพร้อมกับตัวอย่างโค้ดที่ชัดเจนกว่านี้ ควรมีประสิทธิภาพมากกว่าเดิมเล็กน้อย: gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729
bgusach

15
สำหรับ python 3 ให้ใช้ items () แทน iteritems ()
Jangari

127

คุณสามารถสร้างฟังก์ชั่นการวนซ้ำได้ดี

def replace_all(text, dic):
    for i, j in dic.iteritems():
        text = text.replace(i, j)
    return text

โดยที่textเป็นสตริงที่สมบูรณ์และdicเป็นพจนานุกรม - แต่ละนิยามเป็นสตริงที่จะแทนที่การจับคู่กับคำ

หมายเหตุ : ใน Python 3 iteritems()ถูกแทนที่ด้วยitems()


ระวัง:พจนานุกรม Python ไม่มีคำสั่งที่เชื่อถือได้สำหรับการทำซ้ำ วิธีนี้จะแก้ปัญหาของคุณเฉพาะในกรณีที่:

  • คำสั่งของการเปลี่ยนไม่เกี่ยวข้อง
  • มันก็โอเคสำหรับการเปลี่ยนเพื่อเปลี่ยนผลลัพธ์ของการแทนที่ก่อนหน้านี้

ตัวอย่างเช่น

d = { "cat": "dog", "dog": "pig"}
my_sentence = "This is my cat and this is my dog."
replace_all(my_sentence, d)
print(my_sentence)

เอาต์พุตที่เป็นไปได้ # 1:

"นี่คือหมูของฉันและนี่คือหมูของฉัน"

เอาต์พุตที่เป็นไปได้ # 2

"นี่คือสุนัขของฉันและนี่คือหมูของฉัน"

วิธีแก้ไขหนึ่งที่เป็นไปได้คือใช้ OrderedDict

from collections import OrderedDict
def replace_all(text, dic):
    for i, j in dic.items():
        text = text.replace(i, j)
    return text
od = OrderedDict([("cat", "dog"), ("dog", "pig")])
my_sentence = "This is my cat and this is my dog."
replace_all(my_sentence, od)
print(my_sentence)

เอาท์พุท:

"This is my pig and this is my pig."

ระวัง # 2:ประสิทธิภาพถ้าtextสตริงของคุณใหญ่เกินไปหรือมีหลายคู่ในพจนานุกรม


37
ลำดับที่คุณใช้การทดแทนที่แตกต่างกันจะมีความสำคัญ - ดังนั้นแทนที่จะใช้ dict มาตรฐานให้พิจารณาใช้OrderedDict- หรือรายการของ 2-tuples
slothrop

5
สิ่งนี้ทำให้การวนซ้ำสองครั้ง ... ไม่ดีสำหรับการแสดง
Valentin Lorentz

6
ประสิทธิภาพที่ชาญฉลาดเลวร้ายยิ่งกว่าที่วาเลนตินกล่าวไว้ - มันจะสำรวจข้อความหลาย ๆ ครั้งตามที่มีรายการเป็น dic! ใช้ได้ถ้า 'ข้อความ' มีขนาดเล็ก แต่น่ากลัวสำหรับข้อความขนาดใหญ่
JDonner

3
นี่เป็นทางออกที่ดีสำหรับบางกรณี ตัวอย่างเช่นฉันต้องการย่อย 2 ตัวอักษรและฉันไม่สนใจเกี่ยวกับลำดับที่พวกเขาเข้าไปเพราะคีย์การแทนที่ไม่ตรงกับค่าใด ๆ แต่ฉันต้องการให้ชัดเจนว่าเกิดอะไรขึ้น
นาธานการาเบเดียน

5
โปรดทราบว่าสิ่งนี้อาจให้ผลลัพธ์ที่ไม่คาดคิดเนื่องจากข้อความที่แทรกใหม่ในการทำซ้ำครั้งแรกสามารถจับคู่ในการทำซ้ำที่สอง ตัวอย่างเช่นหากเราพยายามแทนที่ 'A' ทั้งหมดด้วย 'B' และ 'B' ทั้งหมดด้วย 'C' สตริง 'AB' จะถูกเปลี่ยนเป็น 'CC' ไม่ใช่ 'BC'
Ambroz Bizjak

106

ทำไมไม่มีวิธีแก้ปัญหาเช่นนี้

s = "The quick brown fox jumps over the lazy dog"
for r in (("brown", "red"), ("lazy", "quick")):
    s = s.replace(*r)

#output will be:  The quick red fox jumps over the quick dog

2
นี่เป็นประโยชน์อย่างยิ่งพกพาง่าย
ฉีก

ดูดี แต่ไม่ได้แทนที่ regex เหมือนใน: สำหรับ r in ((r '\ s.', '.'), (r '\ s,', ',') '):
Martin

2
เพื่อให้เป็น 1 ซับ: เอสเอส = [s.replace (* r) สำหรับ r ใน (("น้ำตาล", "แดง"), ("ขี้เกียจ", "รวดเร็ว"))] [0]
Mark K

95

นี่คือตัวแปรของโซลูชันแรกโดยใช้การลดในกรณีที่คุณต้องการใช้งานได้ :)

repls = {'hello' : 'goodbye', 'world' : 'earth'}
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls.iteritems(), s)

รุ่นที่ดียิ่งขึ้นของ martineau:

repls = ('hello', 'goodbye'), ('world', 'earth')
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls, s)

8
จะง่ายกว่าในการสร้างreplsลำดับของสิ่งอันดับและทำกับการiteritems()โทร คือและrepls = ('hello', 'goodbye'), ('world', 'earth') reduce(lambda a, kv: a.replace(*kv), repls, s)จะทำงานไม่เปลี่ยนแปลงใน Python 3
martineau

ดี! ถ้าคุณใช้ python3 ใช้ไอเท็มแทน iteritems (ตอนนี้ลบออกใน dicts stuff)
e.arbitrio

2
@martineau: ไม่เป็นความจริงที่ว่าสิ่งนี้จะทำงานไม่เปลี่ยนแปลงใน python3 ตั้งแต่reduceถูกลบไปแล้ว
normanius

5
@normanius: reduceยังคงมีอยู่ แต่มันเป็นส่วนหนึ่งของfunctoolsโมดูล (ดูเอกสาร ) ใน Python 3 ดังนั้นเมื่อฉันพูดว่าไม่เปลี่ยนแปลงฉันหมายความว่ารหัสเดียวกันสามารถทำงานได้ - แม้ว่าจะยอมรับว่ามันจำเป็นต้องreduceได้รับการimportแก้ไขหากจำเป็น เนื่องจากไม่มีในตัวอีกต่อไป
martineau

35

นี่เป็นเพียงบทสรุปที่กระชับยิ่งขึ้นของ FJ และ MiniQuark คำตอบที่ดี สิ่งที่คุณต้องมีเพื่อให้ได้การเปลี่ยนสตริงหลาย ๆ อันพร้อมกันคือฟังก์ชั่นต่อไปนี้:

def multiple_replace(string, rep_dict):
    pattern = re.compile("|".join([re.escape(k) for k in sorted(rep_dict,key=len,reverse=True)]), flags=re.DOTALL)
    return pattern.sub(lambda x: rep_dict[x.group(0)], string)

การใช้งาน:

>>>multiple_replace("Do you like cafe? No, I prefer tea.", {'cafe':'tea', 'tea':'cafe', 'like':'prefer'})
'Do you prefer tea? No, I prefer cafe.'

หากคุณต้องการคุณสามารถสร้างฟังก์ชั่นทดแทนเฉพาะของคุณเองโดยเริ่มจากฟังก์ชั่นที่ง่ายกว่านี้


1
ในขณะที่นี่เป็นทางออกที่ดีการแทนที่สตริงที่เกิดขึ้นพร้อมกันจะไม่ให้ผลลัพธ์ที่แม่นยำเหมือนกับการดำเนินการตามลำดับ (การผูกมัด) แต่จะไม่ได้ผล
martineau

2
แน่นอนว่าด้วยrep_dict = {"but": "mut", "mutton": "lamb"}สตริงจะ"button"ส่งผลให้"mutton"มีรหัสของคุณ แต่จะให้"lamb"หากการแทนที่ถูกผูกมัดหนึ่งหลังจากที่อื่น ๆ
martineau

2
นั่นคือคุณสมบัติหลักของรหัสนี้ไม่ใช่ข้อบกพร่อง ด้วยการแทนที่ที่ถูกล่ามโซ่มันไม่สามารถบรรลุพฤติกรรมที่ต้องการในการแทนที่สองคำพร้อมกันและเหมือนในตัวอย่างของฉัน
mmj

1
ดูเหมือนจะเป็นคุณสมบัติที่ยอดเยี่ยมหากคุณไม่ต้องการ แต่ที่นี่เรากำลังพูดถึงการเปลี่ยนพร้อมกันแล้วมันเป็นคุณสมบัติหลัก ด้วยการแทนที่ "ถูกโยง" เอาต์พุตของตัวอย่างจะเป็นDo you prefer cafe? No, I prefer cafe.ซึ่งไม่สามารถทำได้ดีเลย
mmj

@ David เขียนคำตอบของคุณเองการแก้ไขของคุณรุนแรงเกินไป
UmNyobe

29

ฉันสร้างสิ่งนี้ตามคำตอบที่ยอดเยี่ยมของ FJs:

import re

def multiple_replacer(*key_values):
    replace_dict = dict(key_values)
    replacement_function = lambda match: replace_dict[match.group(0)]
    pattern = re.compile("|".join([re.escape(k) for k, v in key_values]), re.M)
    return lambda string: pattern.sub(replacement_function, string)

def multiple_replace(string, *key_values):
    return multiple_replacer(*key_values)(string)

การใช้งานครั้งเดียว:

>>> replacements = (u"café", u"tea"), (u"tea", u"café"), (u"like", u"love")
>>> print multiple_replace(u"Do you like café? No, I prefer tea.", *replacements)
Do you love tea? No, I prefer café.

โปรดทราบว่าเนื่องจากการแทนที่ทำได้เพียงหนึ่งรอบ "คาเฟ่" จะเปลี่ยนเป็น "ชา" แต่จะไม่เปลี่ยนกลับเป็น "คาเฟ่"

หากคุณต้องการเปลี่ยนใหม่หลายครั้งคุณสามารถสร้างฟังก์ชั่นทดแทนได้อย่างง่ายดาย:

>>> my_escaper = multiple_replacer(('"','\\"'), ('\t', '\\t'))
>>> many_many_strings = (u'This text will be escaped by "my_escaper"',
                       u'Does this work?\tYes it does',
                       u'And can we span\nmultiple lines?\t"Yes\twe\tcan!"')
>>> for line in many_many_strings:
...     print my_escaper(line)
... 
This text will be escaped by \"my_escaper\"
Does this work?\tYes it does
And can we span
multiple lines?\t\"Yes\twe\tcan!\"

การปรับปรุง:

  • เปลี่ยนรหัสเป็นฟังก์ชั่น
  • เพิ่มการสนับสนุนหลายบรรทัด
  • แก้ไขข้อผิดพลาดในการหลบหนี
  • ง่ายต่อการสร้างฟังก์ชั่นสำหรับการเปลี่ยนหลายแบบโดยเฉพาะ

สนุก! :-)


1
บางคนสามารถอธิบายทีละขั้นตอนนี้สำหรับงูใหญ่อย่างฉันได้ไหม?
Julian Suarez

เพื่อนหลาม noob ที่นี่ดังนั้นฉันจะถ่ายรูปที่ไม่สมบูรณ์เพื่อทำความเข้าใจกับมัน .. แยก key_values ​​เป็น stuff-to-replace (คีย์ที่รวมโดย "|") และตรรกะ (ถ้าการจับคู่เป็นคีย์ค่าส่งคืน) b สร้าง regex parser ("pattern" ที่มองหาคีย์และใช้ตรรกะที่ให้มาด้วย) - ล้อมรอบด้วยฟังก์ชันแลมบ์ดาและส่งคืน สิ่งที่ฉันกำลังมองหาอยู่ตอนนี้: re.M และความจำเป็นในการแลมบ์ดาเพื่อทดแทนตรรกะ
Fox

1
@ Fox คุณได้รับมัน คุณสามารถกำหนดฟังก์ชั่นแทนการใช้แลมบ์ดามันแค่ทำให้โค้ดสั้นลง แต่ทราบว่าpattern.subคาดว่าฟังก์ชั่นที่มีเพียงหนึ่งพารามิเตอร์ (ข้อความที่จะเปลี่ยน) replace_dictดังนั้นการทำงานต้องมีการเข้าถึง re.Mอนุญาตการแทนที่หลายบรรทัด (อธิบายอย่างดีใน doc: docs.python.org/2/library/re.html#re.M )
MiniQuark

22

ฉันต้องการเสนอการใช้งานแม่แบบสตริง เพียงแค่วางสตริงที่จะถูกแทนที่ในพจนานุกรมและทั้งหมดจะถูกตั้งค่า! ตัวอย่างจากdocs.python.org

>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'
>>> d = dict(who='tim')
>>> Template('Give $who $100').substitute(d)
Traceback (most recent call last):
[...]
ValueError: Invalid placeholder in string: line 1, col 10
>>> Template('$who likes $what').substitute(d)
Traceback (most recent call last):
[...]
KeyError: 'what'
>>> Template('$who likes $what').safe_substitute(d)
'tim likes $what'

ดูดี แต่เมื่อเพิ่มคีย์ที่ไม่ได้ระบุไว้substituteจะทำให้เกิดข้อยกเว้นดังนั้นโปรดระมัดระวังเมื่อรับแม่แบบจากผู้ใช้
Bart Friederichs

2
ข้อเสียเปรียบของวิธีนี้คือเทมเพลตจะต้องมีทั้งหมดและไม่มากไปกว่า $ string ที่จะถูกแทนที่ดูที่นี่
RolfBly

17

ในกรณีของฉันฉันต้องการการแทนที่คีย์ที่ไม่ซ้ำกันด้วยชื่อดังนั้นฉันจึงคิดว่า:

a = 'This is a test string.'
b = {'i': 'I', 's': 'S'}
for x,y in b.items():
    a = a.replace(x, y)
>>> a
'ThIS IS a teSt StrIng.'

3
ใช้งานได้ตราบใดที่คุณไม่มีการแทนที่ หากคุณแทนที่iด้วยsคุณจะได้รับพฤติกรรมแปลก ๆ
bgusach

1
หากคำสั่งซื้อมีความสำคัญแทนที่จะเป็นคำสั่งด้านบนคุณสามารถใช้อาร์เรย์ได้: b = [ ['i', 'Z'], ['s', 'Y'] ]; for x,y in (b): a = a.replace(x, y) จากนั้นหากคุณระมัดระวังในการสั่งซื้อคู่อาร์เรย์ของคุณคุณสามารถมั่นใจได้ว่าคุณจะไม่แทนที่ () ซ้ำ
CODE-READ

ดูเหมือนว่าตอนนี้จะรักษาความสงบเรียบร้อยจาก Python 3.7.0 ฉันทดสอบมันและทำงานได้ตามลำดับบนเครื่องของฉันด้วย Python 3 ที่เสถียรล่าสุด
James Koss

15

การเริ่มต้นPython 3.8และการแนะนำของการกำหนดนิพจน์ (PEP 572) ( :=ตัวดำเนินการ) เราสามารถใช้การแทนที่ภายในรายการความเข้าใจ:

# text = "The quick brown fox jumps over the lazy dog"
# replacements = [("brown", "red"), ("lazy", "quick")]
[text := text.replace(a, b) for a, b in replacements]
# text = 'The quick red fox jumps over the quick dog'

คุณรู้หรือไม่ว่าสิ่งนี้มีประสิทธิภาพมากกว่าการใช้การแทนที่แบบวนซ้ำหรือไม่? ฉันกำลังทดสอบคำตอบทั้งหมดเกี่ยวกับประสิทธิภาพ แต่ฉันยังไม่มี 3.8
Pablo

ทำไมฉันถึงได้ผลลัพธ์ในรายการ?
johnrao07

1
@ johnrao07 ความเข้าใจในรายการเป็นอย่างดีสร้างรายการ ['The quick red fox jumps over the lazy dog', 'The quick red fox jumps over the quick dog']นั่นเป็นเหตุผลที่ในกรณีนี้คุณจะได้รับ แต่การแสดงออกที่ได้รับมอบหมาย ( text := text.replace) ยังสร้างรุ่นใหม่textโดยการกลายพันธุ์ หลังจากรายการเข้าใจคุณสามารถใช้textตัวแปรที่มีข้อความที่แก้ไข
Xavier Guihot

1
หากคุณต้องการส่งคืนเวอร์ชันใหม่ของtextแบบหนึ่งบรรทัดคุณสามารถใช้[text := text.replace(a, b) for a, b in replacements][-1](หมายเหตุ[-1]) ซึ่งแยกองค์ประกอบสุดท้ายของรายการความเข้าใจ textคือรุ่นล่าสุดของ
Xavier Guihot

13

ที่นี่ $ 0.02 ของฉัน มันขึ้นอยู่กับคำตอบของแอนดรูว์คลาร์กชัดเจนขึ้นเล็กน้อยและมันก็ครอบคลุมกรณีที่สตริงที่จะแทนที่เป็นสตริงย่อยของสตริงอื่นเพื่อแทนที่ (ชนะสตริงอีกต่อไป)

def multireplace(string, replacements):
    """
    Given a string and a replacement map, it returns the replaced string.

    :param str string: string to execute replacements on
    :param dict replacements: replacement dictionary {value to find: value to replace}
    :rtype: str

    """
    # Place longer ones first to keep shorter substrings from matching
    # where the longer ones should take place
    # For instance given the replacements {'ab': 'AB', 'abc': 'ABC'} against 
    # the string 'hey abc', it should produce 'hey ABC' and not 'hey ABc'
    substrs = sorted(replacements, key=len, reverse=True)

    # Create a big OR regex that matches any of the substrings to replace
    regexp = re.compile('|'.join(map(re.escape, substrs)))

    # For each match, look up the new string in the replacements
    return regexp.sub(lambda match: replacements[match.group(0)], string)

มันอยู่ในส่วนสำคัญนี้คุณสามารถแก้ไขได้หากคุณมีข้อเสนอใด ๆ


1
นี่ควรเป็นคำตอบที่ยอมรับได้เนื่องจาก regex สร้างขึ้นจากกุญแจทั้งหมดโดยเรียงลำดับตามความยาวจากมากไปน้อยและเข้าร่วมกับ | ตัวดำเนินการสำรอง regex และการเรียงลำดับเป็นสิ่งที่จำเป็นเพื่อให้มีการเลือกตัวเลือกที่ยาวที่สุดที่เป็นไปได้ทั้งหมดหากมีทางเลือกอื่น
Sachin S

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

6

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

def multiple_replace(string, reps, re_flags = 0):
    """ Transforms string, replacing keys from re_str_dict with values.
    reps: dictionary, or list of key-value pairs (to enforce ordering;
          earlier items have higher priority).
          Keys are used as regular expressions.
    re_flags: interpretation of regular expressions, such as re.DOTALL
    """
    if isinstance(reps, dict):
        reps = reps.items()
    pattern = re.compile("|".join("(?P<_%d>%s)" % (i, re_str[0])
                                  for i, re_str in enumerate(reps)),
                         re_flags)
    return pattern.sub(lambda x: reps[int(x.lastgroup[1:])][1], string)

มันใช้ได้กับตัวอย่างที่ให้ไว้ในคำตอบอื่น ๆ เช่น:

>>> multiple_replace("(condition1) and --condition2--",
...                  {"condition1": "", "condition2": "text"})
'() and --text--'

>>> multiple_replace('hello, world', {'hello' : 'goodbye', 'world' : 'earth'})
'goodbye, earth'

>>> multiple_replace("Do you like cafe? No, I prefer tea.",
...                  {'cafe': 'tea', 'tea': 'cafe', 'like': 'prefer'})
'Do you prefer tea? No, I prefer cafe.'

สิ่งสำคัญสำหรับฉันคือคุณสามารถใช้นิพจน์ทั่วไปได้เช่นกันเพื่อแทนที่ทั้งคำเท่านั้นหรือเพื่อทำให้พื้นที่สีขาวเป็นปกติ:

>>> s = "I don't want to change this name:\n  Philip II of Spain"
>>> re_str_dict = {r'\bI\b': 'You', r'[\n\t ]+': ' '}
>>> multiple_replace(s, re_str_dict)
"You don't want to change this name: Philip II of Spain"

หากคุณต้องการใช้คีย์พจนานุกรมเป็นสตริงปกติคุณสามารถหลีกเลี่ยงสิ่งเหล่านั้นก่อนที่จะโทรหลาย ๆ แบบโดยใช้เช่นฟังก์ชันนี้:

def escape_keys(d):
    """ transform dictionary d by applying re.escape to the keys """
    return dict((re.escape(k), v) for k, v in d.items())

>>> multiple_replace(s, escape_keys(re_str_dict))
"I don't want to change this name:\n  Philip II of Spain"

ฟังก์ชันต่อไปนี้สามารถช่วยในการค้นหานิพจน์ทั่วไปที่ผิดพลาดในคีย์พจนานุกรมของคุณ (เนื่องจากข้อความแสดงข้อผิดพลาดจาก multiple_replace ไม่ได้บอกอะไรมาก):

def check_re_list(re_list):
    """ Checks if each regular expression in list is well-formed. """
    for i, e in enumerate(re_list):
        try:
            re.compile(e)
        except (TypeError, re.error):
            print("Invalid regular expression string "
                  "at position {}: '{}'".format(i, e))

>>> check_re_list(re_str_dict.keys())

โปรดทราบว่ามันไม่ได้เชื่อมโยงการแทนที่ สิ่งนี้ทำให้มีประสิทธิภาพมากขึ้นโดยไม่ จำกัด สิ่งที่สามารถทำได้ ในการเลียนแบบเอฟเฟกต์ของการผูกมัดคุณอาจต้องเพิ่มคู่การแทนที่สตริงเพิ่มเติมและให้แน่ใจว่าการเรียงลำดับคู่ที่คาดไว้:

>>> multiple_replace("button", {"but": "mut", "mutton": "lamb"})
'mutton'
>>> multiple_replace("button", [("button", "lamb"),
...                             ("but", "mut"), ("mutton", "lamb")])
'lamb'

นี่เป็นสิ่งที่ดีขอบคุณ สามารถปรับปรุงให้อนุญาตให้ใช้การอ้างอิงย้อนหลังในการทดแทนได้หรือไม่ ฉันไม่ได้คิดวิธีการเพิ่มทันที
cmarqu

คำตอบสำหรับคำถามของฉันด้านบนคือstackoverflow.com/questions/45630940/…
cmarqu

4

นี่คือตัวอย่างที่มีประสิทธิภาพมากขึ้นในสตริงยาวที่มีการเปลี่ยนขนาดเล็กจำนวนมาก

source = "Here is foo, it does moo!"

replacements = {
    'is': 'was', # replace 'is' with 'was'
    'does': 'did',
    '!': '?'
}

def replace(source, replacements):
    finder = re.compile("|".join(re.escape(k) for k in replacements.keys())) # matches every string we want replaced
    result = []
    pos = 0
    while True:
        match = finder.search(source, pos)
        if match:
            # cut off the part up until match
            result.append(source[pos : match.start()])
            # cut off the matched part and replace it in place
            result.append(replacements[source[match.start() : match.end()]])
            pos = match.end()
        else:
            # the rest after the last match
            result.append(source[pos:])
            break
    return "".join(result)

print replace(source, replacements)

ประเด็นอยู่ที่การหลีกเลี่ยงการต่อกันของสตริงที่ยาว เราสับสตริงซอร์สเป็นแฟรกเมนต์แทนที่แฟรกเมนต์บางส่วนในขณะที่เราสร้างรายการจากนั้นเข้าร่วมทุกอย่างกลับเข้าไปในสตริง


2

คุณไม่ควรทำแบบนี้ แต่ฉันคิดว่ามันเจ๋งเกินไป:

>>> replacements = {'cond1':'text1', 'cond2':'text2'}
>>> cmd = 'answer = s'
>>> for k,v in replacements.iteritems():
>>>     cmd += ".replace(%s, %s)" %(k,v)
>>> exec(cmd)

ตอนนี้answerเป็นผลมาจากการเปลี่ยนทั้งหมดในทางกลับกัน

อีกครั้งนี่เป็นแฮ็คมากและไม่ใช่สิ่งที่คุณควรใช้เป็นประจำ แต่เป็นเรื่องดีที่ได้รู้ว่าคุณสามารถทำสิ่งนี้หากคุณต้องการ


2

ฉันกำลังดิ้นรนกับปัญหานี้เช่นกัน ด้วยการแทนที่การแสดงออกปกติจำนวนมากต่อสู้และช้ากว่าการวนซ้ำประมาณสี่เท่าstring.replace(ในเงื่อนไขการทดสอบของฉัน)

อย่างคุณควรลองใช้Flashtextห้องสมุด ( บล็อกโพสต์ที่นี่ , Github ที่นี่ ) ในกรณีของฉันมันเร็วกว่าคำสั่งสองขนาดที่เร็วกว่าจาก 1.8 วิเป็น 0.015 วิ (การแสดงผลปกติใช้เวลา 7.7 วิ)สำหรับแต่ละเอกสาร

มันง่ายที่จะหาตัวอย่างการใช้งานในลิงค์ด้านบน แต่นี่เป็นตัวอย่างการทำงาน:

    from flashtext import KeywordProcessor
    self.processor = KeywordProcessor(case_sensitive=False)
    for k, v in self.my_dict.items():
        self.processor.add_keyword(k, v)
    new_string = self.processor.replace_keywords(string)

โปรดทราบว่า Flashtext ทำการทดแทนในการส่งครั้งเดียว (เพื่อหลีกเลี่ยงการ -> bและb -> cแปล 'a' เป็น 'c') Flashtext ยังค้นหาคำทั้งหมด (ดังนั้น 'คือ' จะไม่ตรงกับ 'th is ') ใช้งานได้ดีหากเป้าหมายของคุณมีหลายคำ (แทนที่ 'This is' by 'Hello')


มันทำงานอย่างไรถ้าคุณต้องการแทนที่แท็ก HTML? เช่นแทนที่ด้วย<p> /nฉันลองแนวทางของคุณแล้ว แต่ด้วยแท็ก flashtext ดูเหมือนจะไม่แยกวิเคราะห์หรือไม่
alias51

1
ฉันไม่แน่ใจว่าทำไมมันไม่ทำงานตามที่คุณคาดหวัง ความเป็นไปได้อย่างหนึ่งคือแท็กเหล่านี้ไม่ได้คั่นด้วยช่องว่างและจำไว้ว่า Flashtext จะค้นหาทั้งคำ วิธีนี้คือการใช้การแทนที่แบบง่ายก่อนเพื่อให้ "สวัสดี <p> นั่น" กลายเป็น "สวัสดี <p> นั่น" คุณจะต้องระมัดระวังในการลบช่องว่างที่ไม่ต้องการออกเมื่อเสร็จแล้ว หวังว่าจะช่วย
ปาโบล

ขอขอบคุณคุณสามารถตั้งค่า<และ>ทำเครื่องหมายจุดสิ้นสุดของคำ (แต่รวมอยู่ในการแทนที่) ได้หรือไม่?
alias51

1
ฉันเชื่อว่า "คำ" ถูกทำเครื่องหมายด้วยช่องว่างเท่านั้น อาจมีพารามิเตอร์ทางเลือกบางตัวที่คุณสามารถตั้งค่าใน "KeywordProcessor" มิฉะนั้นให้พิจารณาวิธีข้างต้น: ทดแทน "<" โดย "<", ใช้ Flashtext จากนั้นแทนที่กลับ (ในกรณีที่คุณตัวอย่างเช่น "<" ถึง "<" และ "\ n" เป็น "\ n" อาจทำงานได้)
Pablo

2

ฉันรู้สึกว่าคำถามนี้ต้องการฟังก์ชั่นแลมบ์ดาแบบบรรทัดคำสั่งแบบเรียกซ้ำเพื่อความสมบูรณ์เพียงเพราะ ดังนั้นที่นั่น:

>>> mrep = lambda s, d: s if not d else mrep(s.replace(*d.popitem()), d)

การใช้งาน:

>>> mrep('abcabc', {'a': '1', 'c': '2'})
'1b21b2'

หมายเหตุ:

  • สิ่งนี้จะใช้พจนานุกรมอินพุต
  • Python dicts รักษาลำดับที่สำคัญเป็น 3.6; คำเตือนที่สอดคล้องกันในคำตอบอื่น ๆ จะไม่เกี่ยวข้องอีกต่อไป สำหรับความเข้ากันได้แบบย้อนกลับเราสามารถใช้เวอร์ชันที่ใช้ tuple:
>>> mrep = lambda s, d: s if not d else mrep(s.replace(*d.pop()), d)
>>> mrep('abcabc', [('a', '1'), ('c', '2')])

หมายเหตุ:เช่นเดียวกับฟังก์ชั่นการเรียกซ้ำทั้งหมดในไพ ธ อนความลึกของการเรียกซ้ำที่ใหญ่เกินไป (เช่นพจนานุกรมการแทนที่ที่มีขนาดใหญ่เกินไป) จะส่งผลให้เกิดข้อผิดพลาด ดูเช่นที่นี่


ฉันพบกับ RecursionError เมื่อใช้พจนานุกรมขนาดใหญ่!
Pablo

@Pablo ที่น่าสนใจ ใหญ่แค่ไหน โปรดทราบว่าสิ่งนี้เกิดขึ้นสำหรับฟังก์ชั่นวนซ้ำทั้งหมด ดูตัวอย่างได้ที่นี่: stackoverflow.com/questions/3323001/…
mcsoini

พจนานุกรมของฉันแทนอยู่ใกล้กับ 100k แง่ ... เพื่อให้ห่างไกลโดยใช้ string.replace คือไกลโดยวิธีที่ดีที่สุด
Pablo

1
@Pablo ในกรณีนั้นคุณไม่สามารถใช้ฟังก์ชันแบบเรียกซ้ำ โดยทั่วไปsys.getrecursionlimit()คือ 1,000 คู่สูงสุด ใช้การวนซ้ำหรืออะไรทำนองนั้นหรือพยายามทำให้การทดแทนง่ายขึ้น
mcsoini

ใช่ฉันกลัวว่าจะไม่มีทางลัดที่นี่
Pablo

1

ฉันไม่รู้เกี่ยวกับความเร็ว แต่นี่เป็นวิธีแก้ปัญหาด่วนของฉันในวันทำงาน:

reduce(lambda a, b: a.replace(*b)
    , [('o','W'), ('t','X')] #iterable of pairs: (oldval, newval)
    , 'tomato' #The string from which to replace values
    )

... แต่ฉันชอบคำตอบที่ # 1 regex ด้านบน หมายเหตุ - ถ้าหนึ่งค่าใหม่เป็นสตริงย่อยของอีกค่าหนึ่งการดำเนินการจะไม่สลับกัน


1

คุณสามารถใช้pandasไลบรารีและreplaceฟังก์ชันที่รองรับทั้งการจับคู่ที่ตรงกันรวมถึงการแทนที่ regex ตัวอย่างเช่น:

df = pd.DataFrame({'text': ['Billy is going to visit Rome in November', 'I was born in 10/10/2010', 'I will be there at 20:00']})

to_replace=['Billy','Rome','January|February|March|April|May|June|July|August|September|October|November|December', '\d{2}:\d{2}', '\d{2}/\d{2}/\d{4}']
replace_with=['name','city','month','time', 'date']

print(df.text.replace(to_replace, replace_with, regex=True))

และข้อความที่แก้ไขคือ:

0    name is going to visit city in month
1                      I was born in date
2                 I will be there at time

คุณสามารถค้นหาตัวอย่างที่นี่ ขอให้สังเกตว่าการแทนที่ข้อความจะทำตามลำดับที่ปรากฏในรายการ


1

สำหรับการแทนที่อักขระเพียงตัวเดียวให้ใช้translateและstr.maketransเป็นวิธีการโปรด

tl; dr> result_string = your_string.translate(str.maketrans(dict_mapping))


การสาธิต

my_string = 'This is a test string.'
dict_mapping = {'i': 's', 's': 'S'}
result_good = my_string.translate(str.maketrans(dict_mapping))
result_bad = my_string
for x, y in dict_mapping.items():
    result_bad = result_bad.replace(x, y)
print(result_good)  # ThsS sS a teSt Strsng.
print(result_bad)   # ThSS SS a teSt StrSng.

0

เริ่มต้นจากคำตอบที่มีค่าของ Andrew i พัฒนาสคริปต์ที่โหลดพจนานุกรมจากไฟล์และทำรายละเอียดไฟล์ทั้งหมดในโฟลเดอร์ที่เปิดเพื่อทำการแทนที่ สคริปต์โหลดการแมปจากไฟล์ภายนอกที่คุณสามารถตั้งค่าตัวคั่นได้ ฉันเป็นผู้เริ่มต้น แต่ฉันพบว่าสคริปต์นี้มีประโยชน์มากเมื่อทำการแทนที่หลายรายการในหลายไฟล์ มันโหลดพจนานุกรมที่มีมากกว่า 1,000 รายการในไม่กี่วินาที มันไม่ได้สง่า แต่มันใช้ได้สำหรับฉัน

import glob
import re

mapfile = input("Enter map file name with extension eg. codifica.txt: ")
sep = input("Enter map file column separator eg. |: ")
mask = input("Enter search mask with extension eg. 2010*txt for all files to be processed: ")
suff = input("Enter suffix with extension eg. _NEW.txt for newly generated files: ")

rep = {} # creation of empy dictionary

with open(mapfile) as temprep: # loading of definitions in the dictionary using input file, separator is prompted
    for line in temprep:
        (key, val) = line.strip('\n').split(sep)
        rep[key] = val

for filename in glob.iglob(mask): # recursion on all the files with the mask prompted

    with open (filename, "r") as textfile: # load each file in the variable text
        text = textfile.read()

        # start replacement
        #rep = dict((re.escape(k), v) for k, v in rep.items()) commented to enable the use in the mapping of re reserved characters
        pattern = re.compile("|".join(rep.keys()))
        text = pattern.sub(lambda m: rep[m.group(0)], text)

        #write of te output files with the prompted suffice
        target = open(filename[:-4]+"_NEW.txt", "w")
        target.write(text)
        target.close()

0

นี่เป็นวิธีแก้ปัญหาของฉัน ฉันใช้มันใน chatbot เพื่อแทนที่คำต่าง ๆ ในคราวเดียว

def mass_replace(text, dct):
    new_string = ""
    old_string = text
    while len(old_string) > 0:
        s = ""
        sk = ""
        for k in dct.keys():
            if old_string.startswith(k):
                s = dct[k]
                sk = k
        if s:
            new_string+=s
            old_string = old_string[len(sk):]
        else:
            new_string+=old_string[0]
            old_string = old_string[1:]
    return new_string

print mass_replace("The dog hunts the cat", {"dog":"cat", "cat":"dog"})

สิ่งนี้จะกลายเป็น The cat hunts the dog


0

อีกตัวอย่างหนึ่ง: รายการอินพุต

error_list = ['[br]', '[ex]', 'Something']
words = ['how', 'much[ex]', 'is[br]', 'the', 'fish[br]', 'noSomething', 'really']

ผลลัพธ์ที่ต้องการจะเป็น

words = ['how', 'much', 'is', 'the', 'fish', 'no', 'really']

รหัส:

[n[0][0] if len(n[0]) else n[1] for n in [[[w.replace(e,"") for e in error_list if e in w],w] for w in words]] 

-2

หรือเพียงแค่แฮ็คที่รวดเร็ว:

for line in to_read:
    read_buffer = line              
    stripped_buffer1 = read_buffer.replace("term1", " ")
    stripped_buffer2 = stripped_buffer1.replace("term2", " ")
    write_to_file = to_write.write(stripped_buffer2)

-2

นี่เป็นอีกวิธีในการทำด้วยพจนานุกรม:

listA="The cat jumped over the house".split()
modify = {word:word for number,word in enumerate(listA)}
modify["cat"],modify["jumped"]="dog","walked"
print " ".join(modify[x] for x in listA)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.