2

I know that I can practically merge two list (in Python 2.7) as follows

list1 = ['one', 'two', 'three', 'four', 'five']
list2 = ['A', 'B', 'C', 'D', 'E']
merged = list1 + list2
print merged
# ['one', 'two', 'three', 'four', 'five', 'A', 'B', 'C', 'D', 'E']

The question is, I would like one of list2 inserted after every two of list1. Example:

list1 = ['one', 'two', 'three', 'four', 'five']
list2 = ['A', 'B', 'C', 'D', 'E']
after 2 of list1:
     add 1 of list2
print merged
# ['one', 'two', 'A', 'three', 'four', 'B', 'five', 'six', 'C', 'seven', 'eight', 'D', 'nine', 'ten']

Any help would be really appreciated!

Taku
  • 31,927
  • 11
  • 74
  • 85
user5740843
  • 1,540
  • 5
  • 22
  • 42

4 Answers4

6

This is the kind of case where using a raw iterator makes for clean code, you can call next on an iterator to get the next value and then append it to the result so the list creation is quite intuitive:

list1 = ['one', 'two', 'three', 'four', 'five']
list2 = ['A', 'B', 'C', 'D', 'E']
iter_list1 = iter(list1)
iter_list2 = iter(list2)

final = []
try: #broken when one of the iterators runs out (and StopIteration is raised)
    while True:
        final.append(next(iter_list1))
        final.append(next(iter_list1))

        final.append(next(iter_list2))
except StopIteration:
    pass
#one will already be empty, add the remaining elements of the non-empty one to the end of the list.
final.extend(iter_list1)
final.extend(iter_list2)

print(final)
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
  • Why not just iterate over `zip(iter_list1, iter_list1, iter_list2)` ? – Chris_Rands May 16 '17 at 15:12
  • because I dislike that idiom with a passion. it is one of the only idioms that the syntax is valid for both iterators and iterables but only works correctly for iterators. – Tadhg McDonald-Jensen May 16 '17 at 15:14
  • As long as you start with `x = iter(x)`, as you do, then that won't be an issue – Chris_Rands May 16 '17 at 15:16
  • As well your method would drop off the last element of `list1` only when it has an odd number of elements and ends before list2. (in this case `"five"` is nowhere to be seen) Trust me, the parallel zip thing is just trouble... – Tadhg McDonald-Jensen May 16 '17 at 15:22
  • Well `zip_longest` then, but I don't think it's un-Pythonic, I've seen Python core developers use this, e.g. Raymond Hettinger https://code.activestate.com/recipes/577916-fast-minmax-function/ – Chris_Rands May 16 '17 at 15:25
  • 6
    @Chris_Rands I am trying to be a teacher not a good coder. The kind of loop I posted is very easy for beginners to manipulate and work with without any obscure issues. Once someone is comfortable with python and iterators and are looking for ways to write more efficient / shorter / easier to read code and have an idea of what to expect from `zip` then absolutely go for it - that's just not the audience I'm writing for. – Tadhg McDonald-Jensen May 16 '17 at 15:48
3

You can try izip_longest for python 2.7 (or zip_longest for python 3+), assuming extra elements from either of the lists will be appended to the result:

from itertools import izip_longest

[y for x in izip_longest(list1[::2], list1[1::2], list2) for y in x if y is not None]
# ['one', 'two', 'A', 'three', 'four', 'B', 'five', 'C', 'D', 'E']

Or use zip if you want to drop unpaired elements:

[y for x in zip(list1[::2], list1[1::2], list2) for y in x]
# ['one', 'two', 'A', 'three', 'four', 'B']
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
Psidom
  • 209,562
  • 33
  • 339
  • 356
  • 2
    The question specifically mentions python-2.7 so you probably should use `itertools.izip_longest` (or mention it at least). Otherwise great answer :) – MSeifert May 16 '17 at 15:03
3

You could use enumerate and list.insert:

>>> l1 = ['A', 'B', 'C', 'D']
>>> l2 = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
>>> l3 = l2[:]  # makes a copy
>>> for idx, item in enumerate(l1):
...     l3.insert((idx*3+2), item)
>>> l3
['one', 'two', 'A', 'three', 'four', 'B', 'five', 'six', 'C', 'seven', 'eight', 'D', 'nine', 'ten']
MSeifert
  • 145,886
  • 38
  • 333
  • 352
1
>>> from operator import add
>>> reduce(add,zip(list1[::2], list1[1::2], list2))
('one', 'two', 'A', 'three', 'four', 'B')

Caution - This will drop the trailing elements.

Explanation:

you can use list slicing like l as list l[low:high:step] to get,

>>> list1[::2]
['one', 'three', 'five']
>>> list1[1::2]
['two', 'four']

With that,

>>> zip(list1[::2], list1[1::2])
[('one', 'two'), ('three', 'four')]

Therefore,

>>> zip(list1[::2], list1[1::2], list2)
[('one', 'two', 'A'), ('three', 'four', 'B')]
>>> reduce(add,zip(list1[::2], list1[1::2], list2))
('one', 'two', 'A', 'three', 'four', 'B')
Keerthana Prabhakaran
  • 3,766
  • 1
  • 13
  • 23