2

I'm trying to replace multiple strings in a list but I'm falling short. The string i want to replace is. The numbers represent the correct order of the words

sentence = ['1', '2', '3', '4']

I want to replace the numbers with text 'i', 'put', 'this', 'here' so that it looks like below

['i', 'put', 'this', 'here']

I found a line of code that manages to replace only one word.

newsentence = [n.replace('1', 'I') for n in sentence]

I attempted to repeat the code 4 times so that it would replace all of the numbers.

newsentence = [n.replace('1', 'I') for n in sentence]
newsentence = [n.replace('2', 'put') for n in sentence]
newsentence = [n.replace('3', 'this') for n in sentence]
newsentence = [n.replace('4', 'here') for n in sentence]

but the result is that the last replacement is executed resulting in

['1', '2', '3', 'here']

Thanks for any feedback

3
  • What are you trying to achieve here? In particular, what are your inputs and desired output? As far as I can see, your inputs are ['1', '2', '3', '4'] / ['i', 'put', 'this', 'here']. Your desired output is ['i', 'put', 'this', 'here']. So there is no problem here?
    – jpp
    Commented Mar 24, 2018 at 14:22
  • I wanted to put those values into the list newsentence. The list ['i', 'put', 'this', 'here'] does not exist yet. Only the numbers list exists. After i replace the numbers with the string then it will exist. Keyur Potdar answered is perfect. If you didn't understand what i was trying to do, you could check out his answer
    – Tazza
    Commented Mar 24, 2018 at 14:36
  • So how do you get your strings 'i', 'put', 'this', 'there'. If they aren't in a list, where are they stored? Almost certainly, this kind of sequential replace is not the road you want to go down. Check out @KeyurPotdar's edit with a dict solution.
    – jpp
    Commented Mar 24, 2018 at 14:38

4 Answers 4

5

Please see the answer of @KeyurPotdar for an explanation for why your original solution did not work.

For a list-comprehension-based solution to your issue (which it seems like you were after), you can create a mapping of inputs to outputs, and then iterate over the mapping with your inputs

mapping = {
    '1': 'i',
    '2': 'put',
    '3': 'this',
    '4': 'here',
}
sentence = ['1', '2', '3', '4']
newsentence = [mapping[word] for word in sentence]
# ['I', 'put', 'this', 'here']

This is nice, but if you decide to throw more inputs at mapping for which you have not defined an output you would get a KeyError. To easily handle this you can use dict.get, which allows you to provide a fallback value to be returned if the given key is missing.

newsentence = [mapping.get(word, word) for word in sentence]
# ['I', 'put', 'this', 'here']

A good reference on dict.get.


Not only is a mapping-based solution more efficient in this case (see @KeyurPotdar's notes on that), but separating your code into data and logic is The Right Thing To Do™.

If you can transform a problem from being code/logic based (such as the sequence of list comprehensions in the original question) to be mapping-based you will almost always win on maintainability and code clarity. Observe that this solution both data and logic are mixed:

newsentence = [n.replace('1', 'I') for n in sentence]
newsentence = [n.replace('2', 'put') for n in newsentence]
newsentence = [n.replace('3', 'this') for n in newsentence]
newsentence = [n.replace('4', 'here') for n in newsentence]

However, in a mapping based solution they are separated

# DATA
mapping = {
    '1': 'i',
    '2': 'put',
    '3': 'this',
    '4': 'here',
}

# LOGIC
newsentence = [mapping.get(word, word) for word in sentence]

What does this buy you? Suppose down the road you end up having to support mapping 1000 words, and these words change often. Having the words mixed with the logic makes them more challenging to find, and makes it harder to mentally decouple if a change will just affect the data or may also accidentally change control flow. With a mapping-based solution one is positive that a change only affects the data.

Consider that we needed to add a mapping of '1a' to 'we'. In the mixed logic/data example it would be very easy to miss changing sentence to newsentence:

newsentence = [n.replace('1', 'I') for n in sentence]
newsentence = [n.replace('1a', 'we') for n in sentence]
newsentence = [n.replace('2', 'put') for n in newsentence]
newsentence = [n.replace('3', 'this') for n in newsentence]
newsentence = [n.replace('4', 'here') for n in newsentence]

Oops! In the mapping-based example this type of error is not possible by design.

Further, by decoupling data from logic one can start storing the data in separate files (such as JSON or YAML). This makes version control a bit more straightforward. It then opens up the possibility to have user-customizable mappings that you do not have to hard-code into your script. Decoupling == good.

2
  • Great explanation! Much needed for all the people who just started using Python (or simply programming in general). Commented Mar 24, 2018 at 18:31
  • I agree (and also with the comment above). In particular the last paragraph is good to show beginner's how data in code can be transformed in configuration files for easy extension without touching the code.
    – progmatico
    Commented Mar 25, 2018 at 23:00
3

Looks like you know which words you need to replace. Using the following approach will do just that in O(n) time.

changes = {
    '1': 'i',
    '2': 'put',
    '3': 'this',
    '4': 'here'
}

sentence = ['1', '2', '3', '4']

newsentence = []
for word in sentence:
    try:
        newsentence.append(changes[word])
    except KeyError:
        newsentence.append(word)

print(newsentence)
# ['i', 'put', 'this', 'here']

Explanation:

What you've to do here is check if the list item is available in the dictionary. If it is available, use it's value and append that value in the new list. Otherwise, append the value of the old list directly.

The code changes[word] in the line newsentence.append(changes[word]) will raise a KeyError if the key is not available in the dictionary. That's why we are catching that error and appending the word directly.

Also, note that this code uses the EAFP principle.


Note: The following code is to only show you where you were going wrong. Avoid using it (reason mentioned below).

To help you understand what's happening in your code, have a look at the following snippet:

>>> sentence = ['1', '2', '3', '4']
>>> newsentence = [n.replace('1', 'I') for n in sentence]
>>> newsentence
['I', '2', '3', '4']
>>> newsentence = [n.replace('2', 'put') for n in sentence]
>>> newsentence
['1', 'put', '3', '4']
>>> newsentence = [n.replace('3', 'this') for n in sentence]
>>> newsentence
['1', '2', 'this', '4']
...

In short, you're replacing a single item from the original sentence, i.e. ['1', '2', '3', '4'] each time. To replace all the items replace sentence with newsentence after the first replace line.

sentence = ['1', '2', '3', '4']

newsentence = [n.replace('1', 'I') for n in sentence]
newsentence = [n.replace('2', 'put') for n in newsentence]
newsentence = [n.replace('3', 'this') for n in newsentence]
newsentence = [n.replace('4', 'here') for n in newsentence]

print(newsentence)
# ['I', 'put', 'this', 'here']

But, avoid this code, as the cost is quadratic; or to be more precise, it's O(m*n) where m is the size of the list and n is the number of words which you want to replace; and obviously it's not feasible to write this if the list is even larger.

8
  • 1
    @KeyurPotdar, +2 for the dictionary solution. -1 for the start bit on sequential replace. Net +1 :). Seems like a classic XY problem.
    – jpp
    Commented Mar 24, 2018 at 14:39
  • @jpp, yes, even I didn't like the replace solution, but I posted that to answer the OP's question as it is. I know it looks like an XY problem. So, in such cases, should I post the first solution and the second, or directly go for the second? Commented Mar 24, 2018 at 14:45
  • @KeyurPotdar, it's a good question. My personal preference is just show the right way to encourage good practice. I'd hate for the user to see your solution and just stick with sequential replace.
    – jpp
    Commented Mar 24, 2018 at 14:46
  • Out of curiosity how does the key value work with the code newsentence.append(changes[word])
    – Tazza
    Commented Mar 24, 2018 at 14:48
  • 2
    A list comprehension equivalent of your solution: newsentence = [changes.get(word, word) for word in sentence]. The second argument to dict.get is the fallback value if the key is missing. Commented Mar 24, 2018 at 17:34
0

You can just do this:

newsentence = [n.replace('1', 'I') for n in sentence]
newsentence = [n.replace('2', 'put') for n in newsentence]
newsentence = [n.replace('3', 'this') for n in newsentence]
newsentence = [n.replace('4', 'here') for n in newsentence]

This means that each time, it changes the previous one, rather than reverting to the original, then just doing one replace. You can also use format():

sentence='{1}{2}{3}{4}{5}'
sentence.format('I', 'put', 'this', 'here')

See this page for more on the format() function.

0

There's a translate method by which you can make multiple replace possible

s = '1 2 3 4'
replaced_string = s.translate(str.maketrans({'1': 'I', '2': 'put', '3': 'this', '4': 'here'}))
print(replaced_string)

#output: I put this here

You can avoid nested replace statements this way Cheers!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.