tags:

views:

593

answers:

8

Suppose I have this list:

[ [5, 44, 73] , [7, 21, 99], [1, 32, 100] ]

What is the MOST efficient way to turn it into this list?

[ 5, 7, 1, 44, 21, 32, 73, 99, 100 ]

Notice, I grab the first from each. Then the 2nd element from each. Of course, this function needs to be done with X elements.

I've tried it, but mine has many loops,and I think it's too long and complicated.

Thanks.

+10  A: 
>>> L1 =  [ [5, 44, 73] , [7, 21, 99], [1, 32, 100] ]
>>> L2 = []
>>> map(L2.extend, zip(*L1))
>>> L2
[5, 7, 1, 44, 21, 32, 73, 99, 100]
truppo
Now, that's pythonic!
mjv
Well, not really.. Using map without using the return value is considered quite bad practise...but it makes the code shorter :)
truppo
Alternatively: list(itertools.chain(*zip(*L1)))
Phil
This has the advantage of being purely functional... if you're into that sort of thing.
Phil
@Phil: posted my answer simultaneously with your comment
Steven Huwig
Phil: How can it be functional, it relies on sideeffects of map
kaizer.se
@kaizer.se: I meant my version with chain and zip, rather than using map to append to a list.
Phil
filter is faster than map, try it. python -mtimeit -n100 -r100 -s'lst = [[5, 44, 73] , [7, 21, 99], [1, 32, 100]]' 'l2=[];filter(l2.extend, zip(*lst))'
gnibbler
+2  A: 

As long as all sublists are the same length:

def flattener(nestedlist):
  if not nestedlist: return []
  return [ x for i in range(len(nestedlist[0]))
             for x in [sublist[i] for sublist in nestedlist]
         ]

For example,

print flattener([ [5, 44, 73] , [7, 21, 99], [1, 32, 100] ])

emits exactly the flat list you desire.

If not all sublist need be the same length, what do you want to happen when some are longer, some shorter...? A precise spec is needed if you need to take such inequality into account.

Alex Martelli
A: 

Here is an O^2 solution, it assumes all the inner arrays are of the same length

parent = [ [5, 44, 73] , [7, 21, 99], [1, 32, 100] ]
final = []
for i in range(len(parent[0])):
    for x in range(len(parent)):
     final.append(parent[x][i])
print final # [5, 7, 1, 44, 21, 32, 73, 99, 100]
Am
-1 for "for i in range(len(listvar)):" - use "for element in listvar:". And where is the O(n^2) part you are apologizing for? Looks O(n) to me.
Paul McGuire
A: 

As long as all the sublists are of the same length:

lst = [[5, 44, 73] , [7, 21, 99], [1, 32, 100]]
list(reduce(lambda l, r: l + r, zip(*lst)))

Edit: This will work with sublists of different lengths:

lst = [[5, 44, 73, 23] , [7, 21, 99], [1, 32, 100]]
list(filter(lambda p: p is not None, reduce(lambda x, y: x + y, map(None, *lst))))
mmarx
-1: `reduce` is rarely the most efficient. Have you measure this against the other answers to see if this is actually most efficient?
S.Lott
-1: import operator;list(reduce(operator.add,zip(*lst))) operator.add will be faster than the lambda
gnibbler
+8  A: 
import itertools
list(itertools.chain(*zip(*L1)))

If you need lists of varying length:

import itertools
[x for x in itertools.chain(*itertools.izip_longest(*L1)) if x is not None]
Steven Huwig
A: 

A simple list comprehension to the second depth:

>>> L = [ [5, 44, 73] , [7, 21, 99], [1, 32, 100] ]
>>> [x for li in zip(*L) for x in li]
[5, 7, 1, 44, 21, 32, 73, 99, 100]

pretty nice. If the sublists are of uneven length, it is not as elegant to express:

>>> L = [ [5, 44, 73] , [7], [1, 32, 100, 101] ]
>>> [li[idx] for idx in xrange(max(map(len, L))) for li in L if idx < len(li)]
[5, 7, 1, 44, 32, 73, 100, 101]

These solutions are of complexity O(n), where n is the total number of elements.

kaizer.se
+4  A: 

"One-liner" != "Pythonic"

It is bad form to use a list comprehension just to implement a for loop in one line. Save the list expressions or generator expressions for times when you want the results of the expression. The clearest and most Pythonic to my eye is (for sublists of equal length):

L1 =  [ [5, 44, 73] , [7, 21, 99], [1, 32, 100] ]
L2 = []
for nextitems in zip(*L1):
    L2.extend(nextitems)

Sure you could write this as:

[ L2.extend(nextitems) for nextitems in zip(*L1) ]

but this generates a list of [None,None,...] as long as the number of items in each sublist, since extend() returns None. And what are we doing with this list? Nothing, and so it gets discarded immediately. But the reader has to look at this for a bit before realizing that this list is "built" for the purpose of running extend() on each created sublist.

The Pythonicness is given by the use of zip and *L1 to pass the sublists of L as args to zip. List comprehensions are generally regarded as Pythonic too, but when they are used for the purpose of creating a list of things, not as a clever shortcut for a for loop.

Paul McGuire
You are right, Paul, the essence of the idiom rests with zip() and the * param; map() made it a one-liner but and gave us this issue with the returns from extend(). I keyed not so much on the one-line (in fact two with L2's initialization), but on the mix of functional programming and powerful built-in list/iterable features, but as said not quite pythonic in this particular use. +1 to you!
mjv
I found Paul's way to be faster than map, but replacing map with filter is faster still
gnibbler
This is my favorite of the answers. Yes, use a for loop to do things more than once; why get tricky with `map()` or `filter()`? By the way, in Python 2.x, you might want to use `itertools.izip()` instead of `zip()`.
steveha
+1  A: 
>>> L1 =  [ [5, 44, 73] , [7, 21, 99], [1, 32, 100] ]
>>> L2 = list(sum(zip(*L1), ()))
>>> L2
[5, 7, 1, 44, 21, 32, 73, 99, 100]
newacct