6

I have a string like

a = "X1+X2*X3*X1"
b = {"X1":"XX0","X2":"XX1","X0":"XX2"}

I want to replace the substring 'X1,X2,X3' using dict b.

However, when I replace using the below code,

for x in b:
    a = a.replace(x,b[x])
print(a)

'XXX2+XX1*X3'

Expected result is XX0 + XX1*X3*XX0

I know it is because the substring is replaced in a loop, but I don't know how to solve it.

Mark Rotteveel
  • 90,369
  • 161
  • 124
  • 175
Jilong Yin
  • 265
  • 2
  • 14
  • Are + and * the only characters separating your elements? – not_speshal Oct 05 '21 at 13:21
  • you are updating the string too often. the easiest way to do this is with the `re` (regex) module. – Rick supports Monica Oct 05 '21 at 13:22
  • See _Split by multiple different delimiters_ at https://note.nkmk.me/en/python-split-rsplit-splitlines-re/ – Sharuzzaman Ahmat Raslan Oct 05 '21 at 13:23
  • keys and values are "incested"... thats why you got a "strange" result – cards Oct 05 '21 at 13:27
  • you should use `print(a)` inside `for`-loop to see what you get after every `replace`. And you should see that after first `replace` you have `XX0 + ....` but later `"X0":"XX2"` will treats `XX0 + ... ` as normal `X0` and it will put `XX2`. – furas Oct 05 '21 at 13:48
  • problem it that old variables `X0`, `X1`, `X2` are similar to new variables `XX0`, `XX1`, `XX2` but if you would use litte different variables ie. `XX!0`, `XX!1`, `XX!2` and use your loop then you could get `XX!0 + XX!1 * X3 * XX!0` and you would need only `replace('!', '')` to get expected `XX0 + XX1 * X3 * XX0` – furas Oct 05 '21 at 13:53
  • There is no pattern in original string, I just want to replace only once in the original string. – Jilong Yin Oct 05 '21 at 13:53
  • the patterns arise in the cycle when you update `a` – cards Oct 05 '21 at 14:00

4 Answers4

8

You can create a pattern with '|' then search in dictionary transform like below.

Try this:

import re
a = "X1+X2*X3*X1"
b = {"X1":"XX0","X2":"XX1","X0":"XX2"}

pattern = re.compile("|".join(b.keys()))
out = pattern.sub(lambda x: b[re.escape(x.group(0))], a)

Output:

>>> out
'XX0+XX1*X3*XX0'
I'mahdi
  • 11,310
  • 3
  • 17
  • 23
  • It seems it is a graceful solution. But I don't understand it yet. I will accept this as answer later. – Jilong Yin Oct 05 '21 at 13:56
  • @JilongYin in this line we create pattern like this : `re.compile("|".join(b.keys())) -> X1|X2|X0` and `"|"` means `or` this come from keys in `dictionary` – I'mahdi Oct 05 '21 at 13:59
  • with `pattern.sub` we search in string and when finding a match with regex we replace that find with value in `dictionary` and we access to value in the dictionary with `b[key]` for multi search and choice we use lambda and for any occur match use value from `dict` – I'mahdi Oct 05 '21 at 14:02
  • 1
    Thank your explanation. It is what I wanted. I can understand it now. Perfect solution. – Jilong Yin Oct 05 '21 at 14:03
  • @JilongYin welcome ;) – I'mahdi Oct 05 '21 at 14:03
4

You can use the repl parameter of re.sub:

import re
re.sub('X\d', lambda x: b.get(x.group(), x.group()), a)

output:

'XX0+XX1*X3*XX0'
mozway
  • 81,317
  • 8
  • 19
  • 49
0

The reason for this is beacuse you are replacing the same string multiple times, so behind the scenes (or between the iterations) there are a few more switches in the middle that you probably don't see (unless debugging this code). Please note that dictionary keys are not ordered, so you cannot assume what's replaced when. I suggest you use template

jsofri
  • 165
  • 7
0

With just built-in functions. The given b dictionary contains cycles between keys and values, so used the f-string notation to perform the substitutions. To escape the {} one should double them {{}}, used for repeated substitution. The enumerate is needed to get unique keys in the new dictionary, so no more cycles.

a = "X1+X2*X3*X1"
b = {"X1":"XX0","X2":"XX1","X0":"XX2"}

new_dict = {}
for i, k in enumerate(b):
    sub_format =  f'{k}' + f'{i}'
    new_dict[sub_format] = b[k]
    a = a.replace(k, f'{{{sub_format}}}')

print(a.format(**new_dict))

Output

XX0+XX1*X3*XX0
cards
  • 2,194
  • 1
  • 3
  • 21