1

I have this list in python:

['Banana', 'Apple', 'John', 'Banana', 'Food', 'Banana']

And I want to replace every second Banana with Pear (this should be the result):

['Pear', 'Apple', 'John', 'Banana', 'Food', 'Pear']

I already have this code:

with open('text,txt') as f:
    words = f.read().split()

words_B = [word if word != 'Banana' else 'Pear' for word in words]
John Winston
  • 25
  • 1
  • 4

4 Answers4

1

You could use a list comprehension to get all indices of Banana and then slicing to get every second of these indices and then just set the corresponding list item to Pear:

>>> l = ['Banana', 'Apple', 'John', 'Banana', 'Food', 'Banana']
>>> for idx in [idx for idx, name in enumerate(l) if name == 'Banana'][::2]:
...     l[idx] = 'Pear'
>>> l
['Pear', 'Apple', 'John', 'Banana', 'Food', 'Pear']

Instead of a comprehension and slicing you could also use a generator expression and itertools.islice:

>>> from itertools import islice
>>> l = ['Banana', 'Apple', 'John', 'Banana', 'Food', 'Banana']
>>> for idx in islice((idx for idx, name in enumerate(l) if name == 'Banana'), None, None, 2):
...     l[idx] = 'Pear'
>>> l
['Pear', 'Apple', 'John', 'Banana', 'Food', 'Pear']

Another possibility, specifically if you don't want to change your list in-place, would be creating your own generator function:

def replace_every_second(inp, needle, repl):
    cnt = 0
    for item in inp:
        if item == needle:    # is it a match?
            if cnt % 2 == 0:  # is it a second occurence?
                yield repl
            else: 
                yield item
            cnt += 1          # always increase the counter
        else:
            yield item

>>> l = ['Banana', 'Apple', 'John', 'Banana', 'Food', 'Banana']
>>> list(replace_every_second(l, 'Banana', 'Pear'))
['Pear', 'Apple', 'John', 'Banana', 'Food', 'Pear']
MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • Is there a way to do it without "special" automatic replace commands? – John Winston Aug 20 '17 at 15:53
  • @JohnWinston Yep, I added one solution using a generator function. Not sure if that's what you meant but it's working and doesn't replace the original. :) – MSeifert Aug 20 '17 at 15:57
  • @JohnWinston `cnt` just counts the number of times the `item == needle` was True - together with the `cnt % 2 == 0` it is used to find out if it's the "second" occurrence that should be replaced. `yield` is more complicated and already covered (in lots of details) in another [question + answer](https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do). – MSeifert Aug 20 '17 at 20:06
0

(Just to point out, your expected result doesn't show you replacing every second Banana with Pear, it shows you replacing the first and third Banana, rather than the second one. If that really is what you want, you can change the shouldReplace = False to shouldReplace = True in my following code.)

MSeifert's solutions are slick and were very informative to me as a beginning Python user, but just to point another way to do it changing your list in place would be something like this:

def replaceEverySecondInstance(searchWord, replaceWord):
    shouldReplace = False
    for index, word in enumerate(words):
        if word == searchWord:
            if shouldReplace == True:
                words[index] = replaceWord
            shouldReplace = not shouldReplace

Running

print(words)
replaceEverySecondInstance('Banana', 'Pear')
print(words)

Gives the following output:

['Banana', 'Apple', 'John', 'Banana', 'Food', 'Banana']
['Banana', 'Apple', 'John', 'Pear', 'Food', 'Banana']
Kdawg
  • 1,508
  • 13
  • 19
0

Here is a generic approach that works by counting down occurances of words since the last replacement:

from collections import defaultdict

d = defaultdict(int)
r = {
    'Banana': 'Pear',
}

fruits = ['Banana', 'Apple', 'John', 'Banana', 'Food', 'Banana']

def replace(fruits, every, first=True):
  for f in fruits:
    # see if current fruit f should be replaced
    if f in r:
        # count down occurence since last change
        # -- start at 1 if first should be changed otherwise 0
        d.setdefault(f, int(not first))
        d[f] -= 1
        # if we have reached count down, replace
        if d[f] < 0:
            yield r[f]
            d[f] = every - 1 
            continue
    # otherwise append fruit as is
    yield f

=> list(replace(fruits, 2, first=True))

['Pear', 'Apple', 'John', 'Banana', 'Food', 'Pear']

=> list(replace(fruits, 2, first=False))

['Banana', 'Apple', 'John', 'Pear', 'Food', 'Banana']

miraculixx
  • 10,034
  • 2
  • 41
  • 60
0

A little convoluted but thought I'd throw it out there. You can use a helper function using itertools.cycle that alternates between Banana and Pear where an element is Banana or just the original value, eg:

from itertools import cycle

data = ['Banana', 'Apple', 'John', 'Banana', 'Food', 'Banana']
b2p = lambda L,c=cycle(['Banana', 'Pear']): next(c) if L == 'Banana' else L
replaced = [b2p(el) for el in data]
# ['Banana', 'Apple', 'John', 'Pear', 'Food', 'Banana']
Jon Clements
  • 138,671
  • 33
  • 247
  • 280