5

I am developing a function in python. Here is my content:

list = ['cow','orange','mango']
to_replace = 'orange'
replace_with = ['banana','cream']

So I want that my list becomes like this after replacement

list = ['cow','banana','cream','mango']

I am using this function:

def replace_item(list, to_replace, replace_with):
    for n,i in enumerate(list):
      if i== to_replace:
         list[n]=replace_with
    return list

It outputs the list like this:

['cow', ['banana', 'cream'], 'mango']

So how do I modify this function to get the below output?

list = ['cow','banana','cream','mango']

NOTE: I found an answer here: Replacing list item with contents of another list but I don't want to involve dictionaries in this. I also want to modify my current function only and keep it simple and straight forward.

Community
  • 1
  • 1
sshussain270
  • 1,785
  • 4
  • 25
  • 49
  • 2
    Better not use `list`, it is a keyword in python. – Jacob Vlijm Sep 11 '16 at 20:44
  • In principle, the replacement you want can be done with `list[n:n+1] = replace_with`. However if you use this statement in your loop, the list will be lengthened but `enumerate()` will only go up to the length of the original list, so 'mango' will get dropped (a hazard of modifying a list while iterating over it). Also, to avoid confusion your function should either leave the original list unchanged and return a new list, or alter the original list and return nothing. Users who execute `new_list = replace_item(lst, to_replace, replace_with)` may be surprised when `lst` is altered. – Matthias Fripp Sep 11 '16 at 22:30

5 Answers5

5

This approach is fairly simple and has similar performance to @TadhgMcDonald-Jensen's iter_replace() approach (3.6 µs for me):

lst = ['cow','orange','mango']
to_replace = 'orange'
replace_with = ['banana','cream']

def replace_item(lst, to_replace, replace_with):
    return sum((replace_with if i==to_replace else [i] for i in lst), [])

print replace_item(lst, to_replace, replace_with)
# ['cow', 'banana', 'cream', 'mango']

Here's something similar using itertools, but it is slower (5.3 µs):

import itertools
def replace_item(lst, to_replace, replace_with):
    return list(itertools.chain.from_iterable(
            replace_with if i==to_replace else [i] for i in lst
    ))

Or, here's a faster approach using a two-level list comprehension (1.8 µs):

def replace_item(lst, to_replace, replace_with):
    return [j for i in lst for j in (replace_with if i==to_replace else [i])]

Or here's a simple, readable version that is fastest of all (1.2 µs):

def replace_item(lst, to_replace, replace_with):
    result = []
    for i in lst:
        if i == to_replace:
            result.extend(replace_with)
        else:
            result.append(i)
    return result

Unlike some answers here, these will do multiple replacements if there are multiple matching items. They are also more efficient than reversing the list or repeatedly inserting values into an existing list (python rewrites the remainder of the list each time you do that, but this only rewrites the list once).

Matthias Fripp
  • 17,670
  • 5
  • 28
  • 45
  • 1
    why do you use `sum` to join lists together? Doesn't that require significantly more overhead then using list comprehension? – Tadhg McDonald-Jensen Sep 11 '16 at 21:02
  • @TadhgMcDonald-Jensen It took me a while to think of a way to replace one item with several items using a list comprehension. Turns out that is a little faster than `sum()` (see above), but a standard loop is even faster. A single-level list comprehension like `[replace_with if i==to_replace else i for i in lst]` would be even faster (0.9 µs), but that doesn't construct the list correctly. – Matthias Fripp Sep 11 '16 at 23:09
4

Modifying a list whilst iterating through it is usually problematic because the indices shift around. I recommend to abandon the approach of using a for-loop.

This is one of the few cases where a while loop can be clearer and simpler than a for loop:

>>> list_ = ['cow','orange','mango']
>>> to_replace = 'orange'
>>> replace_with = ['banana','cream']
>>> while True:
...     try:
...         i = list_.index(to_replace)
...     except ValueError:
...         break
...     else:
...         list_[i:i+1] = replace_with
...         
>>> list_
['cow', 'banana', 'cream', 'mango']
wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    as a general concept this is really great, from an optimization point of view I'd like to point out that `list.index` can take an optional `start` argument to define where it starts looking from which would save a lot of repetitive searching if the list is long with lots of replacements. – Tadhg McDonald-Jensen Sep 11 '16 at 21:20
  • That's a good suggestion. Just initialise `i=0` and add `i + len(replace_with)` as a second positional argument to the replace. – wim Sep 11 '16 at 22:08
3

First off, never use python built-in names and keywords as your variable names (change the list to ls).

You don't need loop, find the index then chain the slices:

In [107]: from itertools import chain    
In [108]: ls = ['cow','orange','mango']
In [109]: to_replace = 'orange'
In [110]: replace_with = ['banana','cream']
In [112]: idx = ls.index(to_replace)  
In [116]: list(chain(ls[:idx], replace_with, ls[idx+1:]))
Out[116]: ['cow', 'banana', 'cream', 'mango']

In python 3.5+ you can use in-place unpacking:

[*ls[:idx], *replace_with, *ls[idx+1:]]
mx0
  • 6,445
  • 12
  • 49
  • 54
Mazdak
  • 105,000
  • 18
  • 159
  • 188
  • I tried your solution, gives out error: 'list' object is not callable – sshussain270 Sep 11 '16 at 20:45
  • @Elisha512 Wim is right, you are using the `list` as your list name, delete it with `del list` and use another name for your list, like mine (`ls`), also never use python built-in names and keywords as your variable names. – Mazdak Sep 11 '16 at 20:49
  • @Kasramvd Your solution works great. But there is one thing that I have assigned values to 'list' variable in my script. Is there any way I can convert it into list without using the callable 'list' in this line: list(chain(ls[:idx], replace_with, ls[idx+1:])) ?? – sshussain270 Sep 11 '16 at 22:09
  • @Elisha512 You can sum the slices. `ls[:idx] + replace_with + ls[idx+1:]` – Mazdak Sep 11 '16 at 22:27
  • @Elisha512 if ever you have a good reason to shadow a builtin but still need it you can use it directly from the builtins module in python 2: `import __builtin__ ; __builtin__.list` or in python 3: `import builtins ; builtins.list`. – Tadhg McDonald-Jensen Sep 12 '16 at 00:53
3

A simple way of representing a rule like this is with a generator, when the to_replace is seen different items are produced:

def iter_replace(iterable, to_replace, replace_with):
    for item in iterable:
        if item == to_replace:
            yield from replace_with
        else:
            yield item

Then you can do list(iter_replace(...)) to get the result you want, as long as you don't shadow the name list.

ls = ['cow','orange','mango']
to_replace = 'orange'
replace_with = ['banana','cream']

print(list(iter_replace(ls,to_replace,replace_with)))

# ['cow', 'banana', 'cream', 'mango']
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
-1
def replace_item(list, to_replace, replace_with):
    index = list.index(to_replace) # the index where should be replaced
    list.pop(index) # pop it out
    for value_to_add in reversed(replace_with):
        list.insert(index, value_to_add) # insert the new value at the right place
    return list
DarkDiamonD
  • 662
  • 6
  • 10