21

Assume I have a list:

myl = [1, 2, 3, 4, 5, 4, 4, 4, 6]

What is the most efficient and simplest pythonic way of in-place (double emphasis) replacement of all occurrences of 4 with 44?

I'm also curious as to why there isn't a standard way of doing this (especially, when strings have a not-in-place replace method)?

lifebalance
  • 1,846
  • 3
  • 25
  • 57
  • 3
    @FrerichRaabe Not a duplicate. More often than not, answers for similar questions don't focus on the in-place requirement. – lifebalance Jun 13 '14 at 09:35
  • @FrerichRaabe -- Not exactly. That doesn't specify the _in-place_ constraint. I'd be surprised if there _isn't_ some other dupe around here, but that's not it ;-) – mgilson Jun 13 '14 at 09:36
  • @mgilson: The other question I referenced doesn't specify a "out-of-place" constraint either. In that sense, it's a superset. Consequently, the answers to the questions give in-place as well as out-of-place (hm, not sure that's a good inverse of in-place :) answers. So I guess you're right - it's not a duplicated but "related". I retracted my vote. – Frerich Raabe Jun 13 '14 at 10:31
  • @FrerichRaabe -- Definitely related. No argument there :-) – mgilson Jun 13 '14 at 10:32
  • @FrerichRaabe I would actually argue [this question here](http://stackoverflow.com/q/2582138/307454) is probably a good candidate for duplicate; as a matter fact, I wanted to post my [answer](http://stackoverflow.com/a/24203748/307454) there, but the question was already closed for various reasons. – lifebalance Jun 13 '14 at 11:17

4 Answers4

30
myl[:] = [x if x != 4 else 44 for x in myl]

Perform the replacement not-in-place with a list comprehension, then slice-assign the new values into the original list if you really want to change the original.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 2
    Only thing is we have to construct a new list and replace the old one with the new one. My version might be efficient here. Have to `timeit` and confirm. – thefourtheye Jun 13 '14 at 09:29
  • @thefourtheye replacing the list comprehension with a generator expression may help. – bereal Jun 13 '14 at 09:35
  • @bereal Please show me how! – lifebalance Jun 13 '14 at 09:38
  • @lifebalance I was thinking about `myl[:] = (x if x != 4 else 44 for x in myl)`, that turned out to be slower, however. – bereal Jun 13 '14 at 09:43
  • 3
    @bereal: [`list_ass_slice`](http://hg.python.org/cpython/file/55fed3eae14b/Objects/listobject.c#l613) calls `PySequence_Fast` on the thing it's assigning to the slice, which kills any benefit of using a generator. – user2357112 Jun 13 '14 at 09:45
  • 1
    @thefourtheye The old list is not replaced with a new one, the old one gets modified in place with slice-assignment. You can check with `id(myl)` before and after slice-assignment, it's still the same afterwards. – Darkonaut Jan 08 '18 at 00:58
21

We can iterate over the list with enumerate and replace the old value with new value, like this

myl = [1, 2, 3, 4, 5, 4, 4, 4, 6]
for idx, item in enumerate(myl):
    if item == 4:
        myl[idx] = 44
print myl
# [1, 2, 3, 44, 5, 44, 44, 44, 6]
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
-2
for  item in myl:
   if item ==4:
      myl[myl.index(item)]=44
apomene
  • 14,282
  • 9
  • 46
  • 72
  • 4
    This doesn't actually do anything to `myl`. – user2357112 Jun 13 '14 at 09:27
  • Lol, I am just trying...very new to python – apomene Jun 13 '14 at 09:31
  • 6
    That's fair enough :) everyone was new at some point, but that's no excuse for writing bad answers that might mislead someone in the future. – Ffisegydd Jun 13 '14 at 09:31
  • Besides, even if you manage to assign it that way, that will iterate through the whole list prefix for every `4` found. Makes `O(n^2)` instead of `O(n)`. – bereal Jun 13 '14 at 09:33
  • @apomene -- I've written many bad answers in my day. Fortunately there's a little "delete" button/link thing so that I could erase it :-) – mgilson Jun 13 '14 at 09:35
  • I will try to improve it as a start... – apomene Jun 13 '14 at 09:36
  • @Ffisegydd Could you show me your code for timing the various answers? – lifebalance Jun 13 '14 at 09:42
  • @Ffisegydd, Appreciate this, as wel as or your comments because I amange to understand my error and fix my code – apomene Jun 13 '14 at 09:43
  • @apomene I'm guessing that your performance might still go further up if you use exceptions? – lifebalance Jun 13 '14 at 09:44
  • @Ffisegydd: If the thing to replace shows up extremely rarely, the overhead of creating tuples and other stuff the other answers do can cause them to run slightly slower. If it shows up more, especially at the end of the list, this answer's performance degrades rapidly. – user2357112 Jun 13 '14 at 09:49
  • @lifebalance here's how to time code: https://stackoverflow.com/questions/7370801/measure-time-elapsed-in-python?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa – Artemis Mar 26 '18 at 19:38
-2
while True:
    try:
        myl[myl.index(4)] = 44
    except:
        break

The try-except approach, although more pythonic, is less efficient. This timeit program on ideone compares at least two of the answers provided herein.

lifebalance
  • 1,846
  • 3
  • 25
  • 57
  • 8
    This isn't more efficient. It only looks so good because you accidentally used brackets for `myl.index[4]` instead of parentheses. It immediately TypeErrors, but the `except` hides the error, so it looks like it finishes instantly. Correct that, and it doesn't look so good. If you move the `4`s later in the list, after the big block of 100 `100`s, or if you add more `4`s, the performance disadvantage of this becomes clearer. Build a list with a high percentage of `4`s, and performance takes a nosedive. – user2357112 Jun 15 '14 at 04:32
  • 1
    @user2357112 Thanks for pointing our the coding error. – lifebalance Jun 16 '14 at 08:07