How do you do replace substrings in Python simultaneously?

Viewed 118

I know you're able to link the .replace() method to make multiple substring replacements.

But there's a problem when I do this:

phrase = "AppleBananaCarrot"
print(phrase.replace("Banana","Apple").replace("Apple","Banana"))

Here, I wanted Banana and Apple to swap, so that it printed: BananaAppleCarrot

Instead it printed: BananaBananaCarrot

In other words: I don't want a replacement substring to be replaced again. The only way I see this being solved is if there was a way to use the .replace() method simultaneously instead of subsequently. Does anyone know how to do that or something similar?

I tried looking on stackoverflow, but the questions were just asking how to do it subsequently more efficiently. I want to do it simultaneously. I couldn't find a question for that.

6 Answers

One trick we can try here is to use re.sub with a callback function:

import re

phrase = "AppleBananaCarrot"
output = re.sub(r'(?:apple|banana)', lambda m: 'Apple' if m.group().lower() == 'banana' else 'Banana', phrase, flags=re.I)
print(output)  # BananaAppleCarrot

The trick here is to make a single pass over the string and use logic to swap Apple for Banana and vice-versa. Note that we could try to do a three step replacement Apple -> (some other string) -> Banana, but the problem with this approach is that there is always the possibility that this intermediate string might happen to already be present in the input.

This could be also a possible solution to that:

>>> f = lambda phrase, w1, w2: w2.join(word.replace(w2, w1) for word in phrase.split(w1))
>>> 
>>> phrase = "AppleBananaCarrot"
>>> print(f(phrase, 'Apple', 'Banana'))
BananaAppleCarrot

You will have to use placeholder (e.g. F1 and F2), otherwise you will not be able to distinguish between the original and the replaced substring. That is a solution if you want to only use String.replace.

phrase.replace("Banana","F1").replace("Apple","F2").replace("F1","Apple").replace("F2","Banana")

Here is my answer:

phrase = "AppleBananaCarrot"
print(phrase.replace("Banana","x").replace("Apple","Banana").replace("x", "Apple"))

you can make a function to do LinFelix answer automatically. The trick is to generate placeholder and check if they are present in the initial string or in newlist. if so, discard and generate another one.

import random
import string

def super_replace(s, oldlist, newlist):
    if len(oldlist) != len(newlist):
        return
        
    #generating placeholders
    phlist = []
    while len(phlist) < len(oldlist):
        ph = "<{[" + ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + "]}>"
        if ph not in s and ph not in phlist and ph not in ''.join(newlist):
            phlist.append(ph)
    
    #replace oldlist with placeholder
    for i in range(len(oldlist)):
        s = s.replace(oldlist[i], phlist[i])
    
    #replace placeholder with newlist
    for i in range(len(oldlist)):
        s = s.replace(phlist[i], newlist[i])
    
    return s
    
    
phrase = "AppleBananaCarrot"
new_phrase = super_replace(phrase, ["Apple", "Banana"], ["Banana", "Apple"])
print(new_phrase)

output:

BananaAppleCarrot

you can also tweak the generation of placeholder so it doesn't required importing random and string by, for example, converting an int to base64

To minimize risk of unwanted behaviour if your substring are small, you can also test if one of the "old" substring is contained in the placeholder.

discard = False
for old in oldlist:
    if old in ph:
        discard = True
if ph not in s and ph not in phlist and ph not in ''.join(newlist) and not discard:
    phlist.append(ph)

You may split the string on capital letters. Swap the first & second sub-strings, then re-join all the three sub-strings:

import re
# Splitting on UpperCase
split_cap = re.findall('[A-Z][^A-Z]*', phrase) 
split_cap[1]+split_cap[0]+split_cap[2]
Related