tags:

views:

137

answers:

4

I want to write a function that takes items from a list and groups them into groups of size n.

Ie, for n = 5, [1, 2, 3, 4, 5, 6, 7] would become [[1, 2, 3, 4, 5], [6, 7]].

What's the best python idiomatic way to do this?

+1  A: 

I don't know of a good command to do this, but here's a way to do it with a list comprehension:

l = [1,2,3,4,5,6,7]
n = 5
newlist = [l[i:i+n] for i in range(0,len(l),n)]

Edit: as a commenter pointed out, I had accidentally put l[i:i+n] in a list.

Matt Boehm
Perfect:) I didn't think of using ranges with steps...
Scotty Allen
So newlist is a list with a single list that contains the lists? Too many levels. Use this: `newlist = [l[i:i+n] for i in range(0,len(l),n)]`
hughdbrown
Or on reading through the entire set of answers: do it like @Greg Hewgill said.
hughdbrown
+2  A: 

You could do this:

[a[x:x+n] for x in range(0, len(a), n)]

(In Python 2, use xrange for efficiency; in Python 3 use range as above.)

Greg Hewgill
A: 
[a[n*k:n*(k+1)] for k in range(0,len(a)/n+1)]
Ofri Raviv
+1  A: 

Solutions using ranges with steps only work on sequences such as lists and tuples (not iterators). They also aren't as efficient as they can be, since they access the sequence many times instead of iterating over it once.

Here's a version which supports iterators and only iterates over the input once, creating a list of lists:

def blockify(iterator, blocksize):
    """Split the items in the given iterator into blocksize-sized lists.

    If the number of items in the iterator doesn't divide by blocksize,
    a smaller block containing the remaining items is added to the result.

    """
    blocks = []
    for index, item in enumerate(iterator):
        if index % blocksize == 0:
            block = []
            blocks.append(block)
        block.append(item)
    return blocks

And now an iterator version which returns an iterator of tuples, doesn't have a memory overhead, and allows choosing whether to include the remainder. Note that the output can be converted into a list via list(blockify(...)).

from itertools import islice

def blockify(iterator, blocksize, include_remainder=True):
    """Split the items in the given iterator into blocksize-sized tuples.

    If the number of items in the iterator doesn't divide by blocksize and
    include_remainder is True, a smaller block containing the remaining items
    is added to the result; if include_remainder is False the remaining items
    are discarded.

    """
    iterator = iter(iterator) # we need an actual iterator
    while True:
        block = tuple(islice(iterator, 5))
        if len(block) < blocksize:
            if len(block) > 0 and include_remainder:
                yield block
            break
        yield block
taleinat
It appears I've re-created an existing recipe by George Sakkis: http://code.activestate.com/recipes/542194/His recipe also supports padding the final block if it's too short, and supplying a function for block creation.
taleinat