0

I needed to make a list of 2-tuples out of a one-dimensional list of coordinates, for example [1, 2, 1, 5] needed to become [(1, 2), (1, 5)]. As it happens I've already found the fastest, most general way to do this, at Activestate, but the code listed there is opaque to a relative Python newbie:

def group3(lst, n):
    return itertools.izip(*[itertools.islice(lst, i, None, n) for i in range(n)])

Can anyone explain what this code is doing in English? What is the asterisk doing in this context?? Thank you.

Ian Durkan
  • 1,212
  • 1
  • 12
  • 26
  • 2
    See my [answer](http://stackoverflow.com/questions/5239856/foggy-on-asterisk-in-python/5239873#5239873) to another question about the asterisk – Cameron Mar 09 '11 at 04:42
  • I can explain it in Python. `itertools` is there for efficiency. Replace it with the respective built-in functions, and it becomes `return zip(*[lst[i::n] for i in range(n)])` which is hopefully easier to understand. – cvoinescu Apr 19 '12 at 23:28

2 Answers2

2

The itertools.islice function can produce a slice from any iterable. The statement:

itertools.islice(lst, 0, None, 2)

In simplified terms means "return an iterator which, when evaluated, will return every other element in lst starting with the element at 0". The 0 arg is the starting point and 2 is the step value (how many to skip to get the next value)

It would be better to use a 6-element list to illustrate, since looking at [(1, 2), (1, 5)] it may be unclear which function produces the inner tuples (they are not produced until the final izip).

>>> lst = [1, 2, 3, 4, 5, 6]
>>> list(itertools.islice(lst, 0, None, 2))
[1, 3, 5]
>>> list(itertools.islice(lst, 0, None, 2))
[2, 4, 6]

Be careful using this with a generator; the first call will traverse the whole sequence, draining the generator:

>>> def foo():
...     for i in range(6):
...         yield i + 1
>>>
>>> gen = foo()
>>> list(itertools.islice(gen, 0, None, 2)
[1, 3, 5]
>>> list(itertools.islice(gen, 1, None, 2)
[]

Your function needs to produce 2 sequences, odds and evens. This is where the list comprehension comes in:

>>> [itertools.islice(lst, i, None, 2) for i in range(2)]
[<itertools.islice object at 0x7f958a79eaf8>, <itertools.islice object at 0x7f958a79eaa0>]

Now you have 2 islice objects ready to be interleaved. To do this we could use zip, but itertools.izip is more efficient because it returns an iterator:

>>> list(zip([1, 3, 5], [2, 4, 6]))
[(1, 2), (3, 4), (5, 6)]
>>> tmp = itertools.izip(*[itertools.islice(lst, i, None, 2) for i in range(2)])
>>> tmp
<itertools.izip object at 0x7f958a7b6cf8>
>>> list(tmp)
[(1, 2), (3, 4), (5, 6)]

Hope that helps clarify the steps.

samplebias
  • 37,113
  • 6
  • 107
  • 103
1

Here's an alternate version for 2-tuples (not as general as the active state recipe):

>>> x = [1,2,3,4,5,6,7,8]
>>> [ (x.pop(0), x.pop(0)) for item in range(len(x)/2) ]
[(1, 2), (3, 4), (5, 6), (7, 8)]

This forms a list of two-tuples by popping the first two numbers off the list until there are no more pairs left.

cprinos
  • 151
  • 2