0

Alright, I am new to Python and am learning about tuples and lists. I have been asked to create a program, however that involves converting the following list:

    li = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]

in to a list which looks like this:

    li = [[(1,2,3),(4,5,6)],[(7,8,9),(10,11,12)],[(13,14,15),(16,17,18)]

Basically, from a simple list to a list containing a sublist which contains two tuples which contain 3 elements.

I appreciate your help.

Thanks.

  • @jozefg: I tried several ways using loops, but they didn't really work..I am still new to the whole thing, so I don't really know that many methods.. – user2390548 Sep 26 '13 at 22:58
  • I understand, but stackoverflow tends to be better about helping to fix code rather than write it for you. Just a word of advice for future questions :) Good luck – daniel gratzer Sep 26 '13 at 23:01
  • @jozefg: Thank you very much. I didn't know that. This was my first question at stackoverflow....thank you for correcting me..Cheers. – user2390548 Sep 26 '13 at 23:13

4 Answers4

2

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.

abarnert
  • 354,177
  • 51
  • 601
  • 671
2

I personally like this approach:

>>> li = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
>>> 
>>> v = iter(li)
>>> li = [(i, next(v), next(v)) for i in v]  # creates list of tuples
>>> 
>>> v = iter(li)
>>> li = [[i, next(v)] for i in v]  # creates list of lists of tuples
>>> 
>>> li
[[(1, 2, 3), (4, 5, 6)], [(7, 8, 9), (10, 11, 12)], [(13, 14, 15), (16, 17, 18)]]

By the way this will raise an error if the elements don't fit in such a structure, for example consider:

>>> l = [1, 2, 3]
>>> v = iter(l)
>>> 
>>> [(i, next(v)) for i in v]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

This may or may not be appropriate.

If it isn't, the easy workaround is to give next() a second argument:

>>> l = [1, 2, 3]
>>> v = iter(l)
>>> 
>>> [(i, next(v, None)) for i in v]
[(1, 2), (3, None)]

Reference:

arshajii
  • 127,459
  • 24
  • 238
  • 287
1

Using zip():

>>> [zip(*[iter(sub)]*3) for sub in zip(*[iter(li)]*6)]
[[(1, 2, 3), (4, 5, 6)], [(7, 8, 9), (10, 11, 12)], [(13, 14, 15), (16, 17, 18)]]
TerryA
  • 58,805
  • 11
  • 114
  • 143
0

Given

li = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]

You can group the items with zip(*[iter(li)]*3):

zip(*[iter(li)]*3)
#>>> <zip object at 0x7f0a5578ca28>

And check:

list(zip(*[iter(li)]*3))
#>>> [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15), (16, 17, 18)]

And then you can do zip(*[...]*2) on the zip(*[iter(li)]*3):

zip(*[zip(*[iter(li)]*3)]*2)
#>>> <zip object at 0x7f0a5578c998>

And check:

list(zip(*[zip(*[iter(li)]*3)]*2))
#>>> [((1, 2, 3), (4, 5, 6)), ((7, 8, 9), (10, 11, 12)), ((13, 14, 15), (16, 17, 18))]

The inner item isn't a list, so map it to one:

map(list, zip(*[zip(*[iter(li)]*3)]*2))
#>>> <map object at 0x7f0a5afd80d0>

And check:

list(map(list, zip(*[zip(*[iter(li)]*3)]*2)))
#>>> [[(1, 2, 3), (4, 5, 6)], [(7, 8, 9), (10, 11, 12)], [(13, 14, 15), (16, 17, 18)]]
Veedrac
  • 58,273
  • 15
  • 112
  • 169
  • Sure, sure. But so does attempting to give a good question before expecting good answers ;). This is actually quite readable when split over 3 lines. – Veedrac Sep 26 '13 at 23:10
  • ah I love one-liners as much as the next guy, but side-by-side argument expanding alongside multiplication...! – hexparrot Sep 26 '13 at 23:11
  • @Veedrac: The fact that it's quite readable when split over 3 lines doesn't help much when you only show it as 1 line, in a form that's likely to scare novices. Why not show the 3-line version? – abarnert Sep 30 '13 at 17:51