1

I want to be able to interleave two lists that could potentially be unequal in length. What I have is:

  def interleave(xs,ys):
    a=xs
    b=ys
    c=a+b
    c[::2]=a
    c[1::2]=b
    return c

This works great with lists that either equal in length or just +/-1. But if let's say xs=[1,2,3] and ys= ["hi,"bye","no","yes","why"] this message appears:

c[::2]=a
ValueError: attempt to assign sequence of size 3 to extended slice of size 4

How do I fix this with using indexing? or do I have to use for loops? EDIT: what I want is to have the extra values just appear at the end.

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
user300
  • 445
  • 2
  • 11
  • 20

4 Answers4

7

You can use itertools.izip_longest here:

>>> from itertools import izip_longest
>>> xs = [1,2,3]
>>> ys = ["hi","bye","no","yes","why"]
>>> s = object()
>>> [y for x in izip_longest(xs, ys, fillvalue=s) for y in x if y is not s]
[1, 'hi', 2, 'bye', 3, 'no', 'yes', 'why']

Using roundrobin recipe from itertools, no sentinel value required here:

from itertools import *
def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).next for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))

Demo:

>>> list(roundrobin(xs, ys))
[1, 'hi', 2, 'bye', 3, 'no', 'yes', 'why']
>>> list(roundrobin(ys, xs))
['hi', 1, 'bye', 2, 'no', 3, 'yes', 'why']
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
7

You can use heapq.merge:

xs = [1, 2, 3]
ys = ['hi', 'bye', 'no', 'yes', 'why']

import heapq
interleaved = [v for i, v in heapq.merge(*[enumerate(el) for el in (xs, ys)])]
# [1, 'hi', 2, 'bye', 3, 'no', 'yes', 'why']

This avoids the need for a sentinel value and flattening.

Use the roundrobin recipe instead to achieve this more effectively without having items be comparable.

Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • It has been drilled into me not to modify the contents of a sequence that is being iterated over with a for loop. Is this a case of, don't do it *unless you know what you are doing*? I'm trying to work out why it is OK in this generator, any hints? – wwii Nov 10 '13 at 17:03
  • @wwii It's not a good idea. (Blame it on an 11pm while tired post). It will work for 2 sequences. However, for consecutively equal length length ones preceding a shorter one - the order of the next items goes "wonky" - using the `roundroubin` recipe is the correct way to do this... I have removed the code and updated answer to that effect. – Jon Clements Nov 11 '13 at 09:33
2

Okay, here's my entry:

>>> from itertools import chain
>>> xs = [1,2,3]
>>> ys = ["hi","bye","no","yes","why"]
>>> xi, yi = iter(xs), iter(ys)
>>> list(chain.from_iterable(zip(xi, yi))) + list(xi) + list(yi)
[1, 'hi', 2, 'bye', 3, 'no', 'yes', 'why']

Alternatively,

>>> [i for row in zip(xi, yi) for i in row] + list(xi) + list(yi)

would have worked too (that's just the listcomp idiom for flattening, as used by @hcwhsa). My first thought was

>>> list(zip(*sorted(list(enumerate(xs)) + list(enumerate(ys)))))[1]
(1, 'hi', 2, 'bye', 3, 'no', 'yes', 'why')

but that's just a much less efficient version of @Jon Clements (I used an inefficient sort, he used an efficient heap queue.)

[I've been experimenting trying to get something using cycle working, but it doesn't seem to be as easy as I'd hoped: and it turns out that I was simply working toward reimplementing the roundrobin recipe that @hcwsha posted, so there's no point in finishing that. :^) ]

DSM
  • 342,061
  • 65
  • 592
  • 494
  • We're actually chatting a bit about this in the Python room - On Python 3.x, our approaches will break because of the need to do comparisons.... :) – Jon Clements Nov 09 '13 at 23:09
  • @Jon Clements: Hey, my official entry will work, there are no comparisons there. :^) – DSM Nov 09 '13 at 23:16
  • Yeah... I had noticed I'd also re-written a non-itertools version of roundrobin :) – Jon Clements Nov 09 '13 at 23:18
0

Kept it simple:

def interleave(xs,ys):
    stop = min(len(xs), len(ys))
    suffix = max(xs, ys, key = len)[stop:]
    out = list()
    for pair in zip(xs, ys):
        out.extend(pair)
    out.extend(suffix)
    return out

Caveats:
Python 2.7
Assumes lists are passed as the arguments.

wwii
  • 23,232
  • 7
  • 37
  • 77
  • I think your `out += xs[index] + ys[index]` line would need to be `out += [xs[index], ys[index]]`, or use 1-element slices or something to make sure you're concatenating lists. And there's no `xrange` in Python 3. But those are easily fixed. – DSM Nov 09 '13 at 23:41
  • @DSM, I tried it on a single test case and it works, ```interleave(list('ab'), list('cdefghijklmnop'))```. Should I state that my answers are Python 2? Is there an assumption, here, that answers are 3 unless stated otherwise? – wwii Nov 10 '13 at 03:56
  • Your test case is a little special because they're one-character strings. Try `[1,2,3]` and `['hi', 'bye', 'no', 'yes', 'why']`, or even `['ab','cd'],['ef','gh']`. As for the Python 3 thing, I'm sorry -- I was sure that the question had the python 3 tag, and it doesn't seem to. – DSM Nov 10 '13 at 04:07
  • Thanks for the nudge. Fixed. While fixing it, reminded me of something I'd already seen. – wwii Nov 10 '13 at 04:48
  • what if I'm not allowed to use any functions other than : len, range, list, str, print, and .append? I have a function for finding the minimum so I've ajusted that. But I don't understand how I can go about replacing the .extend @wwii – user300 Nov 10 '13 at 06:59
  • zip() creates a list of tuples. Instead of extending ```out``` with the tuple, you can unpack the tuple, ```for first, second in zip(xs, ys):```, and append first and second to out. To append suffix you would need to iterate over suffix and append each element to out. You might want to add those (and any other) specs to your question. – wwii Nov 10 '13 at 16:14