1

I have a list

['r', 'o', 'c', 'o', 'c', 'o']` 

and wish to make it

[['r','o'], ['c', 'o'], ['c', 'o']]

how would I do this? moreover, I need to make the new list grouped to whatever "n" is in the above example, n is 2 if n is 3, the result should be: [['r', 'o', 'c']['o','c','o']]

Sukrit Kalra
  • 33,167
  • 7
  • 69
  • 71

3 Answers3

7

The itertools recipes have a general-purpose function that does exactly what you're looking for with any kind of iterator, called grouper:

>>> values = ['r', 'o', 'c', 'o', 'c', 'o']
>>> groups = grouper(values, 3)

However, this returns you an iterator. If you want a list, you have to ask for one explicitly:

>>> groups = list(grouper(values, 3))
>>> print(groups)
[('r', 'o', 'c'), ('o', 'c', 'o')]

Also, note that this gives you a list of tuples, not a list of lists. Most likely this doesn't actually matter to you. But if it does, you'll have to convert them:

>>> list_groups = [list(group) for group in grouper(values, 3)]
>>> print(list_groups)
[['r', 'o', 'c'], ['o', 'c', 'o']]

If you install more_itertools off PyPI, you can just from more_itertools import grouper. Otherwise, you'll have to copy and paste from the recipes into your code.

But either way, it's worth understanding how grouper works:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return zip_longest(fillvalue=fillvalue, *args)

First, it creates an iterator out of your iterable. (This is something that keeps track of its current position and returns values one by one as you call next on it, until you've reached the end.) Then it makes n references to that iterator. This is the tricky bit—you don't want n separate iterators to the same list, you want n references to the same iterator, so if you grab the next value out of the first iterator, they all move forward. That's why it does the funny [iter(iterable)] * n bit. Then it just zips the iterators together. So, the first pass through the zip calls next on the first iterator, then the second, then the third; the second pass through the zip again calls next on the first iterator, then the second, then the third; and so on.

The reason it uses zip_longest instead of just zip (or, in Python 2.x, izip_longest vs. izip) is so list(grouper(['r', 'o', 'c', 'o'], 3)) will give you [('r', 'o', 'c'), ('o', None, None)] instead of just [('r', 'o', 'c')]. If that's not what you want, it's trivial to just use the other function instead.

For further explanation, see this blog post.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • This is a more comprehensive version of my answer (which I deleted). +1, this is the correct way to do it. – arshajii Aug 01 '13 at 19:41
  • @arshajii: But your answer was more concise, which is why I upvoted it… I guess we don't really need two versions of the same thing, but I liked yours. – abarnert Aug 01 '13 at 19:43
5

Something like this?

>>> a = ['r', 'o', 'c', 'o', 'c', 'o']
>>> zip(*[iter(a)]*2)
[('r', 'o'), ('c', 'o'), ('c', 'o')]

>>> zip(*[iter(a)]*3)
[('r', 'o', 'c'), ('o', 'c', 'o')]

You can change the 2 and 3 to the number you want.

Sukrit Kalra
  • 33,167
  • 7
  • 69
  • 71
  • 1
    this is good, but other solutions are more readable imo – RodericDay Aug 01 '13 at 19:36
  • A potential problem here is that elements will start getting left out if the list can't be evenly partitioned. Try this with `4`, for example. – arshajii Aug 01 '13 at 19:40
  • @arshajii: That may or may not be what you want, of course. Usually it isn't, in which case you need to use `zip_longest` (and optionally pass a `fillvalue`). – abarnert Aug 01 '13 at 19:42
  • By the way, this is effectively equivalent to the `grouper` recipe; the only reason it's less readable is that it isn't wrapped up in a function (with a nice name for the function and params, and possibly comments). And, as I said in my answer, it really is worth being able to understand why this answer works. – abarnert Aug 02 '13 at 18:20
  • Yes, it just uses `zip()` instead of `zip_longest()`, should have used that though. Your answer was perfect @abarnert. :) – Sukrit Kalra Aug 02 '13 at 18:21
  • @SukritKalra: Well, maybe 70% of the time you want `zip_longest`, 25% of the time you want `zip`, so really, it's worth having (and explaining) both answers… – abarnert Aug 02 '13 at 19:30
0
[l[i:i+n] for i in range(0, len(l), n)]
Vlad K.
  • 300
  • 3
  • 11