23

One may want to do the contrary of flattening a list of lists, like here: I was wondering how you can convert a flat list into a list of lists.

In numpy you could do something like:

>>> a=numpy.arange(9)
>>> a.reshape(3,3)
>>> a
array([[0, 1, 2],
   [3, 4, 5],
   [6, 7, 8]])

I was wondering how you do the opposite, and my usual solution is something like:

>>> Mylist
['a', 'b', 'c', 'd', 'e', 'f']
>>> newList = []
for i in range(0,len(Mylist),2):
...     newList.append(Mylist[i], Mylist[i+1])
>>> newList 
[['a', 'b'], ['c', 'd'], ['e', 'f']]

is there a more "pythonic" way to do it?

Davide Fiocco
  • 5,350
  • 5
  • 35
  • 72
oz123
  • 27,559
  • 27
  • 125
  • 187
  • its is best not to use `list` as a variable name as there is the builtin function `list()` – jamylak Apr 12 '12 at 13:41
  • yeap you are right, I was just editing the code fast, my original code does not look like that. – oz123 Apr 12 '12 at 13:46

4 Answers4

39
>>> l = ['a', 'b', 'c', 'd', 'e', 'f']
>>> zip(*[iter(l)]*2)
[('a', 'b'), ('c', 'd'), ('e', 'f')]

As it has been pointed out by @Lattyware, this only works if there are enough items in each argument to the zip function each time it returns a tuple. If one of the parameters has less items than the others, items are cut off eg.

>>> l = ['a', 'b', 'c', 'd', 'e', 'f','g']
>>> zip(*[iter(l)]*2)
[('a', 'b'), ('c', 'd'), ('e', 'f')]

If this is the case then it is best to use the solution by @Sven Marnach

How does zip(*[iter(s)]*n) work

Community
  • 1
  • 1
jamylak
  • 128,818
  • 30
  • 231
  • 230
  • 3
    This solution only works if there are enough items to fill it, otherwise items get cut off. – Gareth Latty Apr 12 '12 at 13:44
  • Creating a generator then duplicating a reference to it; clever. – Nick T Apr 23 '13 at 00:13
  • 1
    @NickT well technically it's an iterator not a generator and I can't take credit for the cleverness :) – jamylak Apr 23 '13 at 00:29
  • I see that it works, but this seems scary and obscure. Is it a good idea to rely on zip accessing the two copies of iter(l) in such an order, as to produce the desired result?? – ToolmakerSteve Dec 15 '13 at 03:54
  • 2
    To produce a list of lists: `map(list,zip(*[iter(l)]*2))`, or `map(list,zip(*[iter(l)]*3))`, etc. – Robert May 29 '14 at 13:27
  • @jamylak, Can you please explain why this operation doesn't result in -- [('a','a'), ('b','b').....] in output because you are making 2 copies of the iterator and zip would be drawing from both of them simultaneously. – Azrael Aug 30 '15 at 23:14
  • @Azreal Because it's not making 2 copies of the iterator, it's making 2 references to the exact same iterator. Which is drawing from the single iterator one after the other – jamylak Aug 31 '15 at 00:41
  • can u please explain the syntax and why does it work? – Hanan Shteingart Apr 06 '16 at 21:12
13

This is usually done using the grouper recipe from the itertools documentation:

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

Example:

>>> my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> list(grouper(2, my_list))
[('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', None)]
Steven C. Howell
  • 16,902
  • 15
  • 72
  • 97
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
10

Another way to create a list of lists can be simplified as shown below:

>>>MyList = ['a','b','c','d','e','f']
# Calculate desired row/col
>>>row = 3
>>>col = 2
>>>NewList = [MyList[col*i : col*(i+1)] for i in range(row)]
>>>NewList
[['a', 'b', 'c'], ['d', 'e', 'f']]

This can method can be extended to produce any row and column size. If you select row and column values such that row*col >len(MyList), the sublist (row) containing the last value in MyList will end there, and NewList will simply be filled with the appropriate number of empty lists to satisfy the row/col specifications

>>>MyList = ['a','b','c','d','e','f','g','h']
>>>row = 3
>>>col = 3
>>>NewList = [MyList[col*i : col*(i+1)] for i in range(row)]
>>>NewList
[['a', 'b', 'c'], ['d', 'e', 'f'], ['g','h']]

>>>row = 4
>>>col = 4
>>>NewList = [MyList[col*i : col*(i+1)] for i in range(row)]
[['a', 'b', 'c', 'd'], ['e', 'f', 'g','h'], [], []]
Ryan M
  • 711
  • 6
  • 9
  • 3
    I guess that should be the accepted answer as the question was asking for a list of lists and not a list of tuples. Or the answer by Robert in the comment above. – Costas B. Jun 21 '15 at 21:00
0

If one prefers a list of lists, rather than a list of tuples from a flat list, then one could do this:

    a = range(20) # sample starting list 
    b = [] # new list
    c = [] # alternate new list
    # ny is length of new list. nx length of each list within it
    nx = 5; ny = 4 
    bb = 0; ee = bb + nx # option one: sliding indeces for slices.
    for ii in range(ny-1):
        bb += nx
        ee += nx
        b.append(a[bb:ee])
        c.append(a[slice(ii*nx,nx*(ii+1))]) # option two, use slice()

(I've played around with shrinking the whole for loop into one line with list comprehensions, but have not been successful. the way I've used it, slice() can almost get you there.) One possible advantage of these approaches over the others mentioned is that if your original, flat list is not an even multiple of the dimensions of your new, desired list of lists, you won't lose any data. The caveat is that the last list will be shorter than all the others, as it will contain the "leftovers". granted, neither of these methods strikes me as very pythonic.

  • I don't understand why you're using `slice()` in the second approach, rather than the slice syntax. Doing a calculation doesn't mean you can't use `a[ii*nx:nx*(ii+1)]` or whatever. – Blckknght Apr 29 '15 at 03:56