46

Is there a far shorter way to write the following code?

my_string = my_string.replace('A', '1')
my_string = my_string.replace('B', '2')
my_string = my_string.replace('C', '3')
my_string = my_string.replace('D', '4')
my_string = my_string.replace('E', '5')

Note that I don't need those exact values replaced; I'm simply looking for a way to turn 5+ lines into fewer than 5

8 Answers 8

74

Looks like a good opportunity to use a loop:

mapping = { 'A':'1', 'B':'2', 'C':'3', 'D':'4', 'E':'5'}
for k, v in mapping.iteritems():
    my_string = my_string.replace(k, v)

A faster approach if you don't mind the parentheses would be:

mapping = [ ('A', '1'), ('B', '2'), ('C', '3'), ('D', '4'), ('E', '5') ]
for k, v in mapping:
    my_string = my_string.replace(k, v)
Sign up to request clarification or add additional context in comments.

6 Comments

It's odd; that's the way I'd write it, because the problem is all about mapping from one character to another. But really, you're not using the dict as a dict; you're using it as a list of tuples. So why not write a list of tuples in the first place?
Certainly a list of tuples would also work (and be faster). I'll edit the answer to include that option.
What if you will have this mapping { 'A':'1', '1':'A'} probably the result will not be the same as expected
A more concise way to define mapping = zip(list('ABCDE'),range(1,6))
For Python 3, use .items() instead of .iteritems()
|
51

You can easily use string.maketrans() to create the mapping string to pass to str.translate():

import string
trans = string.maketrans("ABCDE","12345")
my_string = my_string.translate(trans)

4 Comments

this is nice, but doesn't work in the case of multi-character patterns or replacements
Correct. I assumed from the question that it was character-based replacement.
This is actually the only correct way to replace simultaneously a list of characters. For example, if you do '<>'.replace('<', '>').replace('>','<') you will get '<<', but with translation you get '><'.
in python 3 it is str.maketrans("ABCDE", "12345")
17

If you want to get the wrong answer, slowly, then use string.replace in a loop. (Though it does work in this case of no overlap among the patterns and replacements.)

For the general case with possible overlaps or a long subject string, use re.sub:

import re

def multisub(subs, subject):
    "Simultaneously perform all substitutions on the subject string."
    pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs)
    substs = [s for p, s in subs]
    replace = lambda m: substs[m.lastindex - 1]
    return re.sub(pattern, replace, subject)

>>> multisub([('hi', 'bye'), ('bye', 'hi')], 'hi and bye')
'bye and hi'

For the special case of 1-character patterns and 1- or 0-character replacements, use string.maketrans.

3 Comments

+1 on the approach of using re.sub, -1 for saying that string.replace gives the wrong answer, since it's not clear exactly what should happen for overlapping matches. So net 0 :)
str.replace can give an arguably wrong answer. For dictionary-based solutions, consider the string "wow" and translation table {'w': 'foo', 'o': 'bar'}. Depending on the order that the dict iterates, you can end up with different results: "fbarbarbarfbarbar" or "foobarfoo". A function that gives different output for equal input is arguably broken.
Miles, yes, that's the kind of problem I meant. You could also run into overlaps in the keys alone, like 'hello' and 'hell'.
14

Also look into str.translate(). It replaces characters according to a mapping you provide for Unicode strings, or otherwise must be told what to replace each character from chr(0) to chr(255) with.

Comments

9
replaceDict = {'A':'1','B':'2','C':'3','D':'4','E':'5'}       
for key, replacement in replaceDict.items():  
  my_string = my_string.replace( key, replacement )

Comments

7

I think it could be a little more efficient:

mapping = { 'A':'1', 'B':'2', 'C':'3', 'D':'4', 'E':'5'}
my_string = "".join([mapping[c] if c in mapping else c for c in my_string])

I suggest some benchmark with "timeit", with real cases in base of the lenght of "my_string".

2 Comments

this is the best solution IMO if you have multiple replacements at once and if the replacements are not always single characters.
Finally someone used a dictionary as it it was intended. All others are O(n * m) but your method is O(n * log(m)) or better, where m is the size of the dictionary
2

You can do it in one line using Pandas.

import pandas as pd

my_string="A B C test"

my_string =pd.DataFrame([my_string])[0].replace(["A","B","C","D","E"],['1','2','3','4','5'],regex=True)[0]

print(my_string)
'1 2 3 test'

Comments

1

One way I do it is with an associated array (a dictionary). Here is an example of the replacements I use when getting a file ready for deployment in LaTeX using regular expressions.

  import re
  def escapeTexString(string): # Returns TeX-friendly string
    rep = { # define desired replacements in this dictionary (mapping)
         '&': '\\&',
         '%': '\\%',
         '#': '\\#',
         '_': '\\_',
         '{': '\\{', # REGEX Special
         '}': '\\}', # REGEX Special
         '~': '\\char"007E{}', # LaTeX Special
         '$': '\\$', # REGEX Special
         '\\': '\\char"005C{}', # REGEX/LaTeX Special
         '^': '\\char"005E{}', # REGEX/LaTeX Special
         '"': '\\char"FF02{}'
        }
    # use these two lines to do the replacement (could be shortened to one line)
    pattern = re.compile("|".join(map(re.escape,rep.keys()))) # Create single pattern object (key to simultaneous replacement)
    new_string = pattern.sub(lambda match: rep[match.group(0)], string)
    return new_string

1 Comment

Please read the other answers before posting. Your answer does exactly the same thing as Darius' answer from 8 years earlier.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.