The easy way to solve this is to do it in two steps: First, break it down into a list of 3-tuples. Then, break that down into a list of 2-lists.
There are two ways to do each step.
The first is using slicing and ranging:
>>> li = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
>>> [li[i:i+3] for i in range(0, len(li), 3)]
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18]]
But we want 3-tuples, not 3-lists, so we need to add a tuple()
around the expression:
>>> li_3 = [tuple(li[i:i+3]) for i in range(0, len(li), 3)]
And then, we just do the same thing again:
>>> li_23 = [li_3[i:i+2] for i in range(0, len(li_3), 2)]
The other way to do it is by zipping the same iterator to itself. The itertools
recipes in the docs show how to do this. In fact, let's just copy and paste the recipe, and then we can use it.
>>> from itertools import izip_longest
>>> 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 izip_longest(fillvalue=fillvalue, *args)
Now:
>>> li = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
>>> grouper(li, 3)
<itertools.zip_longest at 0x10467dc00>
It's giving us an iterator; we have to turn it into a list to see the elements:
>>> list(grouper(li, 3))
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18]]
So, now we just repeat the same step:
>>> list(grouper(grouper(li, 3), 2))
[((1, 2, 3), (4, 5, 6)), ((7, 8, 9), (10, 11, 12)), ((13, 14, 15), (16, 17, 18))]
Except we're getting tuples of tuples, and we want lists of tuples, so:
>>> li_23 = [list(group) for group in grouper(grouper(li, 3), 2)]
There's supposed to be one and only one obvious way to do things in Python. So, which is it?
- The first requires a list or other sequence; the second takes any iterable.
- If you have leftover values at the end, the first gives you a partial group; the second pads the last group.
- The second one is very easy to modify to drop an incomplete last group instead of padding it, and not too hard to modify to give you a partial group; the first is harder to modify.
- The first is easier to understand for a novice, but probably harder to read once you get the concept.
- Either one can be easily wrapper up in a function, but the second one is already wrapped up for you in the recipes (or the
more_itertools
third-party package).
One of these differences is usually going to matter for your use case, in which case the obvious way to do it is the one that does what you want. For simple cases like this, where none of it matters… do whichever one you find more readable.