Hoe vervang ik meerdere substrings van een string?

Ik wil de functie .replace gebruiken om meerdere strings te vervangen.

Ik heb momenteel

string.replace("condition1", "")

maar zou graag zoiets willen hebben

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

hoewel dat geen goede syntaxis lijkt

wat is de juiste manier om dit te doen? een beetje zoals hoe je in grep/regex \1en \2kunt doen om velden te vervangen door bepaalde zoekreeksen


Antwoord 1, autoriteit 100%

Hier is een kort voorbeeld dat het zou moeten doen met reguliere expressies:

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)

Bijvoorbeeld:

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

Antwoord 2, autoriteit 49%

Je zou gewoon een leuke kleine looping-functie kunnen maken.

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

waarbij textde volledige tekenreeks is en diceen woordenboek is — elke definitie is een tekenreeks die een overeenkomst met de term zal vervangen.

Opmerking: in Python 3 is iteritems()vervangen door items()


Let op:Python-woordenboeken hebben geen betrouwbare volgorde voor iteratie. Deze oplossing lost uw probleem alleen op als:

  • volgorde van vervangingen is niet relevant
  • het is oké dat een vervanger de resultaten van eerdere vervangers wijzigt

Update: de bovenstaande verklaring met betrekking tot de volgorde van invoeging is niet van toepassing op Python-versies groter dan of gelijk aan 3.6, omdat standaarddictaten zijn gewijzigd om invoegvolgorde voor iteratie te gebruiken.

Bijvoorbeeld:

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

Mogelijke uitvoer #1:

"Dit is mijn varken en dit is mijn varken."

Mogelijke uitvoer #2

"Dit is mijn hond en dit is mijn varken."

Een mogelijke oplossing is het gebruik van een 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)

Uitvoer:

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

Voorzichtig #2:Inefficiënt als uw text-tekenreeks te groot is of als er veel paren in het woordenboek staan.


Antwoord 3, Autoriteit 43%

Waarom niet een oplossing als deze?

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

Antwoord 4, Autoriteit 33%

Hier is een variant van de eerste oplossing het gebruik te verminderen, voor het geval je alsof je functioneel. 🙂

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

martineau is nog betere versie:

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

Antwoord 5, Autoriteit 13%

Dit is slechts een beknopte samenvatting van F.J en MiniQuark goede antwoorden en last but duidelijke verbetering door bgusach. Alles wat je nodig hebt om meerdere gelijktijdige reeks vervangingen te bereiken, is de volgende functie:

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)

Gebruik:

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

Als u wilt, kunt u uw eigen dedicated vervangende functies vanaf dit eenvoudiger te maken.


Antwoord 6, Autoriteit 9%

Ik bouwde dit op F.J.s uitstekende antwoord:

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)

Eenmalig gebruik:

>>> 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é.

Houd er rekening mee dat aangezien de vervanging in slechts één keer wordt gedaan, “café” verandert in “thee”, maar niet terug in “café”.

Als u dezelfde vervanging vaak moet uitvoeren, kunt u eenvoudig een vervangingsfunctie maken:

>>> 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!\"

Verbeteringen:

  • veranderde code in een functie
  • ondersteuning voor meerdere lijnen toegevoegd
  • een bug opgelost bij het ontsnappen
  • eenvoudig een functie maken voor een specifieke meervoudige vervanging

Veel plezier! 🙂


Antwoord 7, autoriteit 8%

Het starten van Python 3.8en de introductie van toewijzingsexpressies (PEP 572)(:=operator), we kunnen de vervangingen toepassen binnen een lijstbegrip:

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

Antwoord 8, autoriteit 7%

Ik wil het gebruik van tekenreekssjablonen voorstellen. Plaats gewoon de string die moet worden vervangen in een woordenboek en alles is ingesteld! Voorbeeld van 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'

Antwoord 9, autoriteit 6%

In mijn geval had ik een eenvoudige vervanging van unieke sleutels door namen nodig, dus ik bedacht dit:

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.'

Antwoord 10, Autoriteit 5%

Hier mijn $ 0,02. Het is gebaseerd op Andrew Clark’s antwoord, slechts een beetje duidelijker, en het dekt ook de zaak wanneer een te vervangen tekenreeks een substring van een andere reeks is om te vervangen (langere string wint)

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)

Het is in deze deze gist , voel je vrij om het te wijzigen als je een voorstel hebt.


Antwoord 11, Autoriteit 2%

Ik had een oplossing nodig waar de te vervangen reeks een reguliere uitdrukkingen kunnen zijn,
Bijvoorbeeld om te helpen bij het normaliseren van een lange tekst door meerdere witruimtekens te vervangen door een enkele. Gebouw op een keten van antwoorden van anderen, inclusief miniquark en MMJ, dit is waar ik mee bedacht:

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)

Het werkt voor de voorbeelden in andere antwoorden, bijvoorbeeld:

>>> 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.'

Het belangrijkste voor mij is dat je ook reguliere expressies kunt gebruiken, bijvoorbeeld om alleen hele woorden te vervangen, of om witruimte te normaliseren:

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

Als u de woordenboeksleutels als normale tekenreeksen wilt gebruiken,
je kunt die ontsnappen voordat je multiple_replace aanroept met b.v. deze functie:

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"

De volgende functie kan helpen bij het vinden van foutieve reguliere expressies tussen uw woordenboeksleutels (aangezien de foutmelding van multiple_replace niet erg veelzeggend is):

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

Houd er rekening mee dat het de vervangingen niet aan een ketting plaatst, maar ze tegelijkertijd uitvoert. Dit maakt het efficiënter zonder te beperken wat het kan doen. Om het effect van chaining na te bootsen, moet je misschien gewoon meer string-vervangende paren toevoegen en zorgen voor de verwachte volgorde van de paren:

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

Antwoord 12

Opmerking: test uw zaak, zie opmerkingen.

Hier is een voorbeeld dat efficiënter is op lange snaren met veel kleine vervangingen.

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)

Het punt is om veel aaneenschakelingen van lange snaren te vermijden. We hakken de bronstring naar fragmenten, het vervangen van enkele van de fragmenten als we de lijst vormen, en vervolgens bij het hele ding weer in een touwtje.


Antwoord 13

U kunt de pandasBibliotheek en de replaceFunctie vervangen die zowel exacte overeenkomsten als RegEx-vervangingen ondersteunt. Bijvoorbeeld:

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

en de gewijzigde tekst is:

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

Je kunt een voorbeeld vinden hier. Merk op dat de vervangingen van de tekst worden gedaan in de volgorde waarin ze in de lijsten voorkomen


Antwoord 14

Ik worstelde ook met dit probleem. Met veel vervangingen hebben reguliere expressies het moeilijk en zijn ze ongeveer vier keer langzamer dan het herhalen van string.replace(in mijn experimentomstandigheden).

Probeer absoluut de bibliotheek Flashtextte gebruiken (blogpost hier, Github hier). In mijn gevalwas het een beetje voorbij twee ordes van grootte sneller, van 1,8 s tot 0,015 s (reguliere uitdrukkingen duurden 7,7 s)voor elk document.

Het is gemakkelijk om gebruiksvoorbeelden te vinden in de bovenstaande links, maar dit is een werkend voorbeeld:

   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)

Houd er rekening mee dat Flashtext in één keer substituties maakt (om te voorkomen dat a –> ben b –> c‘a’ in ‘c’ vertalen ). Flashtext zoekt ook naar hele woorden (dus ‘is’ komt niet overeen met ‘this‘). Het werkt prima als je doel uit meerdere woorden bestaat (waarbij ‘Dit is’ wordt vervangen door ‘Hallo’).


Antwoord 15

Ik heb het gevoel dat deze vraag voor de volledigheid een recursief lambda-functie-antwoord met één regel nodig heeft, gewoon omdat. Dus daar:

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

Gebruik:

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

Opmerkingen:

  • Dit verbruikt het invoerwoordenboek.
  • Python-dictaten behouden de sleutelvolgorde vanaf 3.6; overeenkomstige kanttekeningen in andere antwoorden zijn niet meer relevant. Voor achterwaartse compatibiliteit kan men een beroep doen op een op tupel gebaseerde versie:
>>> mrep = lambda s, d: s if not d else mrep(s.replace(*d.pop()), d)
>>> mrep('abcabc', [('a', '1'), ('c', '2')])

Opmerking:Zoals bij alle recursieve functies in Python, zal een te grote recursiediepte (d.w.z. te grote vervangende woordenboeken) resulteren in een fout. Zie bijv. hier.


Antwoord 16

Gebruik voor het vervangen van slechts één teken de translateen str.maketransis mijn favoriete methode.

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


demo

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.

Antwoord 17

Je zou het echt niet zo moeten doen, maar ik vind het gewoon veel te cool:

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

Nu is answerhet resultaat van alle vervangingen op hun beurt

nogmaals, dit is erghacky en is niet iets dat je regelmatig zou moeten gebruiken. Maar het is gewoon leuk om te weten dat je zoiets kunt doen als dat ooit nodig is.


Antwoord 18

Ik weet niets over snelheid, maar dit is mijn dagelijkse snelle oplossing:

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

… maar ik hou van het #1 regex-antwoord hierboven. Opmerking – als een nieuwe waarde een subtekenreeks is van een andere, is de bewerking niet commutatief.


Antwoord 19

Vanaf het kostbare antwoord van Andrew heeft ik een script ontwikkeld dat het woordenboek uit een bestand laadt en alle bestanden op de geopende map uitvoert om de vervanging uit te voeren. Het script laadt de toewijzingen van een extern bestand waarin u de separator kunt instellen. Ik ben een beginner, maar ik vond dit script erg handig bij het doen van meerdere substituties in meerdere bestanden. Het laadde een woordenboek met meer dan 1000 inzendingen in seconden. Het is niet elegant, maar het werkte voor mij

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

Antwoord 20

dit is mijn oplossing voor het probleem. Ik gebruikte het in een chatbot om de verschillende woorden tegelijk te vervangen.

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

dit wordt The cat hunts the dog


Antwoord 21

Een ander voorbeeld:
Invoerlijst

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

De gewenste uitvoer zou zijn

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

Code:

[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]] 

Antwoord 22

Mijn aanpak zou zijn om eerst de tekenreeks te tokeniseren en vervolgens voor elke token te beslissen of deze moet worden opgenomen of niet.

Misschien beter presterend, als we kunnen aannemen dat O(1) opzoeken voor een hashmap/set:

remove_words = {"we", "this"}
target_sent = "we should modify this string"
target_sent_words = target_sent.split()
filtered_sent = " ".join(list(filter(lambda word: word not in remove_words, target_sent_words)))

filtered_sentis nu 'should modify string'


Antwoord 23

Of gewoon voor een snelle hack:

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)

Antwoord 24

Hier is een andere manier om het met een woordenboek te doen:

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)

Other episodes