views:

205

answers:

3

How can I yield multiple items at a time from an iterable object?

For example, with a sequence of arbitrary length, how can I iterate through the items in the sequence, in groups of X consecutive items per iteration?

(Question inspired by an answer which used this technique.)

+4  A: 

Your question is a bit vague, but check out the grouper recipe in the itertools documentation.

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

(Zipping the same iterator several times with [iter(iterable)]*n is an old trick, but encapsulating it in this function avoids confusing code, and it is the same exact form and interface many people will use. It's a somewhat common need and it's a bit of a shame it isn't actually in the itertools module.)

Mike Graham
AFAIK `[iter(iterable)]*n` is dirty because the execution order of function arguments is not defined, i.e. you cannot be sure that the first argument of a function get evaluated first. This might not be a problem as long as you use the same interpreter but may produce weird results with other interpreters.
Michael
@Michael, the order is guaranteed, see here: http://docs.python.org/library/itertools.html#itertools.izip
Matt Joiner
@Michael, I don't see why you would think that, order is quite well-defined.
Mike Graham
Right, that's not a problem here. What I meant: When you write something like `f(3+4, 5+6)` then it's not defined whether `3+4` or `5+6` is evaluated first. But that's not a problem here because both arguments are the same and `iter` gets evaluated before.
Michael
@Michael: The evaluation order is guaranteed. `f(x,y)` will always evaluate `x` before `y`. See http://docs.python.org/reference/expressions.html#evaluation-order.
unutbu
Hm, didn't know that one, looks like both are guaranteed.
Matt Joiner
Of course, with the particular example of `f(3+4, 5+6)` they may well be constantfolded and the expressions not evaluated anyhow...
Mike Graham
A: 

Just use itertools.groupby

KangOl
How can `itertools.groupby` be used to "iterate through the items in the sequence, in groups of X consecutive items per iteration"?
Laurence Gonsalves
Indeed, I don't believe this is correct. Nor do I understand what groupby even does :)
Matt Joiner
You just need a sufficiently advanced key function. Off the cuff, http://paste.pocoo.org/show/173972/ . (Of course, it is *also* true that this is probably not what was meant.) Probably not good for actual use.
Devin Jeanpierre
Yeah, itertools.groupby is a little weird. It takes an iterable and a "key function". The function is applied to each element of the iterable, to produce a "key" for each element. The elements are then "grouped by" these keys and you get back an iterable of tuples where each tuple consists of a key, and an iterable of all of the (consecutive) elements from the input iterable that mapped to that key. Because of the consecutive restriction (presumably there so that it can be more lazy) you generally want to sort your input on the same key.
Laurence Gonsalves
@Devin: Clever, but probably *too* clever. :-)
Laurence Gonsalves
@Laurence I'm actually on the fence about it. To me, it seems a lot more intuitive than zipping an iterator with itself n times. The biggest practical issue with that code is that it involves a constantly increasing integer, but the count() iterator can be replaced by `itertools.cycle(xrange(n*2))` or something. However, it's not a real suggestion because, well, the grouper() recipe is standard.
Devin Jeanpierre
+1  A: 

Here's another approach that works on older version of Python that don't have izip_longest:

def grouper(n, seq):
  result = []
  for x in seq:
    result.append(x)
    if len(result) >= n:
      yield tuple(result)
      del result[:]
  if result:
    yield tuple(result)

No filler, so the last group might have fewer than n elements.

Laurence Gonsalves
Note that all the functions in `itertools` have pure Python implementations in the documentation if someone is on a Python older then 2.6.
Mike Graham