2

Similar to this question, but instead of replacing one item with another, I'd like to replace any occurrences of one item with the contents of a list.

orig = [ 'a', 'b', 'c', 'd', 'c' ]
repl = [ 'x', 'y', 'z' ]
desired = [ 'a', 'b', 'x', 'y', 'z', 'd', 'x', 'y', 'z' ]

# these are all incorrect, or fail to compile
[ repl if x == 'c' else x for x in orig ]
[ [a for a in orig] if x == 'c' else x for x in orig ]
[ (a for a in orig) if x == 'c' else x for x in orig ]
[ a for a in orig if x == 'c' else x for x in orig ]

Edit: made it clear I meant to replace all occurrences of the item, rather than just the first. (Apologies to anyone who didn't cover that case in their answer.)

Community
  • 1
  • 1
moswald
  • 11,491
  • 7
  • 52
  • 78

5 Answers5

6
>>> orig = [ 'a', 'b', 'c', 'd' ]
>>> repl = [ 'x', 'y', 'z' ]
>>> desired = list(orig)  #can skip this and just use `orig` if you don't mind modifying it (and it is a list already)
>>> desired[2:3] = repl
>>> desired
['a', 'b', 'x', 'y', 'z', 'd']

And of course, if you don't know that 'c' is at index 2, you can use orig.index('c') to find out that information.

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • Deleted my answer as its almost same as yours :-) – Abhijit Feb 19 '13 at 16:55
  • You should do `desired = list(orig)` to make it more readable for beginners and to make valid the claim of working on any type of repl and orig – JBernardo Feb 19 '13 at 17:04
  • 1
    @JBernardo -- Fair enough :) Good suggestion. Edited. Although, beginners should learn that `x[:]` is a shorthand way to copy a list -- (*statement without proof to follow*) It might be the fastest way to copy a list ... – mgilson Feb 19 '13 at 17:06
  • Keep calling orig.index('c') until it finally throws ValueError? That's expensive and the code is rather awkward looking too. – tdelaney Feb 19 '13 at 17:26
  • 1
    @tdelaney -- when I posted, there was only 1 'c' in `orig` and no indication that OP wanted to replace multiple occurances. – mgilson Feb 19 '13 at 17:46
  • @mgilson - sorry about that. I must have read the question after the update. – tdelaney Feb 19 '13 at 20:32
4

Different approach: when I'm doing replacements, I prefer to think in terms of dictionaries. So I'd do something like

>>> orig = [ 'a', 'b', 'c', 'd' ]
>>> rep = {'c': ['x', 'y', 'z']}
>>> [i for c in orig for i in rep.get(c, [c])]
['a', 'b', 'x', 'y', 'z', 'd']

where the last line is the standard flattening idiom.

One advantage (disadvantage?) of this approach is that it'll handle multiple occurrences of 'c'.

[update:]

Or, if you prefer:

>>> from itertools import chain
>>> list(chain.from_iterable(rep.get(c, [c]) for c in orig))
['a', 'b', 'x', 'y', 'z', 'd']

On the revised test case:

>>> orig = [ 'a', 'b', 'c', 'd', 'c' ]
>>> rep = {'c': ['x', 'y', 'z']}
>>> list(chain.from_iterable(rep.get(c, [c]) for c in orig))
['a', 'b', 'x', 'y', 'z', 'd', 'x', 'y', 'z']
DSM
  • 342,061
  • 65
  • 592
  • 494
  • 3
    Advantage! I should have made that clear in my question that I needed all instances replaced, not just the first. – moswald Feb 19 '13 at 17:05
  • @DSM -- That's the standard flattening idiom? Somewhere along the way I missed that one -- Maybe it's because I still have a hard time parsing it :). `itertools.chain` for me ... – mgilson Feb 19 '13 at 17:08
  • @mgilson: well, it's the standard *listcomp* flattening idiom. :^) – DSM Feb 19 '13 at 17:10
  • Now that you've added `chain`, I'll upvote that :). Although, it's worth pointing out that this answer imposes hashability on the list elements (at least for the ones you want to replace). Most of the time, that's not a big deal, but occasionally it might be. – mgilson Feb 19 '13 at 17:48
2

No need for anything fancy:

desired = orig[:2] + repl + orig[3:]

To find 2 you can search for orig.index('c').

x = orig.index('c')
desired = orig[:x] + repl + orig[x+1:]

if repl is not a list, just use list(repl)

JBernardo
  • 32,262
  • 10
  • 90
  • 115
  • 1
    fair enough (+1) ... Although this only works if the type of `repl` is the same as the type of `orig`. My answer will even work if `repl` is a generator :-) – mgilson Feb 19 '13 at 16:54
0

If you enumerate backwards, you can extend the list as you go because the items you move have already gone through the enumeration.

>>> orig = [ 'a', 'b', 'c', 'd', 'c' ]
>>> repl = [ 'x', 'y', 'z' ]
>>> desired = [ 'a', 'b', 'x', 'y', 'z', 'd', 'x', 'y', 'z' ]
>>> for i in xrange(len(orig)-1, -1, -1):
...     if orig[i] == 'c':
...             orig[i:i+1] = repl
... 
>>> orig
['a', 'b', 'x', 'y', 'z', 'd', 'x', 'y', 'z']
tdelaney
  • 73,364
  • 6
  • 83
  • 116
0

Yet another way:

>>> import operator
>>> orig = [ 'a', 'b', 'c', 'd', 'c' ]
>>> repl = [ 'x', 'y', 'z' ]
>>> output = [repl if x == 'c' else [x] for x in orig]
>>> reduce(operator.add, output)
['a', 'b', 'x', 'y', 'z', 'd', 'x', 'y', 'z']
>>> 
shantanoo
  • 3,617
  • 1
  • 24
  • 37