34

Input:

intersperse(666, ["once", "upon", "a", 90, None, "time"])

Output:

["once", 666, "upon", 666, "a", 666, 90, 666, None, 666, "time"]

What's the most elegant (read: Pythonic) way to write intersperse?

Claudiu
  • 216,039
  • 159
  • 467
  • 667

15 Answers15

39

I would have written a generator myself, but like this:

def joinit(iterable, delimiter):
    it = iter(iterable)
    yield next(it)
    for x in it:
        yield delimiter
        yield x
Jeff Mercado
  • 121,762
  • 30
  • 236
  • 257
  • 1
    note: the first arg could be called `iterable`. And the function could be called just `joinit()`. btw, the nice thing about your solution that it also works for an empty sequence (`next()` raises `StopIteration` and the generator `joinseq()` returns immediately). – jfs Apr 15 '11 at 12:03
  • @J.F.: Thanks for the tip. Naming it `iterable` would have been the first thing I did. But instead, I got into the habit of calling it `sequence` as everyone else has. :) – Jeff Mercado Apr 15 '11 at 18:18
  • 4
    I like the pun of `joinit` meaning both "join it" and "join it[erable]" – Claudiu Nov 20 '13 at 18:30
  • 4
    @claudiu I like that you came back two years later to point that out :) – undergroundmonorail Jan 13 '16 at 01:10
20

itertools to the rescue
- or -
How many itertools functions can you use in one line?

from itertools import chain, izip, repeat, islice

def intersperse(delimiter, seq):
    return islice(chain.from_iterable(izip(repeat(delimiter), seq)), 1, None)

Usage:

>>> list(intersperse(666, ["once", "upon", "a", 90, None, "time"])
["once", 666, "upon", 666, "a", 666, 90, 666, None, 666, "time"]
jfs
  • 374,366
  • 172
  • 933
  • 1,594
Felix Kling
  • 756,363
  • 169
  • 1,062
  • 1,111
  • Ah back to the last version. :) – Jeff Mercado Apr 13 '11 at 21:49
  • @JeffMercado: Yes, thanks for your comment ( I think it was you?) :) It works indeed... – Felix Kling Apr 13 '11 at 21:50
  • @JeffMercado: I know.... synchronisation problem ;) Not sure if this is really efficient though... so many iterators.... I like your solution :) But it is nice to know more than one way (despite Pythons motto that there is only one ;)) – Felix Kling Apr 13 '11 at 21:54
  • 1
    Note that the expansion of the arguments for `chain` requires iterating over all of `s`. This is a big problem for something like `intersperse(666, repeat(777))`, which the generator answers handle fine. You should be able to fix this by using `chain.from_iterable(izip(...))` instead. – Andrew Clark Apr 13 '11 at 22:03
  • @Andrew: Oh I didn't know. But makes sense now that I think about it... Thank you, will put this in my answer. – Felix Kling Apr 13 '11 at 22:05
  • This would be elegant if Python's syntax were different, I think – Claudiu Jul 04 '14 at 15:10
17

Another option that works for sequences:

def intersperse(seq, value):
    res = [value] * (2 * len(seq) - 1)
    res[::2] = seq
    return res
Sven Marnach
  • 530,615
  • 113
  • 910
  • 808
6

Solution is trivial using more_itertools.intersperse:

>>> from more_itertools import intersperse
>>> list(intersperse(666, ["once", "upon", "a", 90, None, "time"]))
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time']

Technically, this answer isn't "writing" intersperse, it's just using it from another library. But it might save others from having to reinvent the wheel.

Jonathan Sudiaman
  • 2,364
  • 1
  • 10
  • 14
3

I would go with a simple generator.

def intersperse(val, sequence):
    first = True
    for item in sequence:
        if not first:
            yield val
        yield item
        first = False

and then you can get your list like so:

>>> list(intersperse(666, ["once", "upon", "a", 90, None, "time"]))
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time']

alternatively you could do:

def intersperse(val, sequence):
    for i, item in enumerate(sequence):
        if i != 0:
            yield val
        yield item

I'm not sure which is more pythonic

cobbal
  • 68,517
  • 19
  • 142
  • 155
  • i like this the best so far: no slicing, no doing anything specific to the iterator like calling `len`.. it aint a 1 liner but it looks nicer than the 1 liners – Claudiu Apr 13 '11 at 21:38
3
def intersperse(word,your_list):
    x = [j for i in your_list for j in [i,word]]

>>> intersperse(666, ["once", "upon", "a", 90, None, "time"])
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time', 666]

[Edit] Corrected code below:

def intersperse(word,your_list):
    x = [j for i in your_list for j in [i,word]]
    x.pop()
    return x

>>> intersperse(666, ["once", "upon", "a", 90, None, "time"])
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time']
Phillip
  • 51
  • 2
2

How about:

from itertools import chain,izip_longest

def intersperse(x,y):
     return list(chain(*izip_longest(x,[],fillvalue=y)))
rmalouf
  • 3,195
  • 1
  • 14
  • 10
2

The basic and easy you could do is:

a = ['abc','def','ghi','jkl']

# my separator is : || separator ||
# hack is extra thing : --

'--|| separator ||--'.join(a).split('--')

output:

['abc','|| separator ||','def','|| separator ||','ghi','|| separator ||','jkl']
DARK_C0D3R
  • 1,749
  • 15
  • 20
  • 2
    I like this one liner for small lists, but I'd suggest using a more unique delimiter. '--' seems like a common enough string that it'll bite someone down the line. Use something like '$)($' instead. – Ryan McGrath Jun 11 '21 at 03:27
1

I just came up with this now, googled to see if there was something better... and IMHO there wasn't :-)

def intersperse(e, l):    
    return list(itertools.chain(*[(i, e) for i in l]))[0:-1]
Nir Friedman
  • 16,242
  • 2
  • 39
  • 67
1

I believe this one looks pretty nice and easy to grasp compared to the yield next(iterator) or itertools.iterator_magic() one :)

def list_join_seq(seq, sep):
  for i, elem in enumerate(seq):
    if i > 0: yield sep
    yield elem

print(list(list_join_seq([1, 2, 3], 0)))  # [1, 0, 2, 0, 3]
Ben Usman
  • 7,179
  • 5
  • 42
  • 64
1

Dunno if it's pythonic, but it's pretty simple:

def intersperse(elem, list):
    result = []
    for e in list:
      result.extend([e, elem])
    return result[:-1]
sverre
  • 6,560
  • 2
  • 26
  • 35
  • i thought of that, but i don't like that you slice the list at the end – Claudiu Apr 13 '11 at 21:21
  • I could go with initialising result to list[0], but then I'd have to first check if list is empty. [][:-1] conveniently returns [] sparing me from that problem. Seems like the generator answer is the only one so far that doesn't employ slicing. – sverre Apr 13 '11 at 21:31
0

This works:

>>> def intersperse(e, l):
...    return reduce(lambda x,y: x+y, zip(l, [e]*len(l)))
>>> intersperse(666, ["once", "upon", "a", 90, None, "time"])
('once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time', 666)

If you don't want a trailing 666, then return reduce(...)[:-1].

Seth
  • 42,464
  • 10
  • 85
  • 118
0

Seems general and efficient:

def intersperse(lst, fill=...):
    """
    >>> list(intersperse([1,2,3,4]))
    [1, Ellipsis, 2, Ellipsis, 3, Ellipsis, 4]
    """
    return chain(*zip(lst[:-1], repeat(fill)), [lst[-1]])
dawid
  • 591
  • 3
  • 10
0

You can use Python's list comprehension:

def intersperse(iterable, element):
    return [iterable[i // 2] if i % 2 == 0 else element for i in range(2 * len(iterable) - 1)]
-1
def intersperse(items, delim):
    i = iter(items)
    return reduce(lambda x, y: x + [delim, y], i, [i.next()])

Should work for lists or generators.

Art Vandelay
  • 144
  • 2
  • 8