4

Say that you have a list of lists, of lists, for example

lll = [(['a', 'b'], [1, 2], [True, False]),
       (['c'], [3], [True]),
       (['d', 'e', 'f', 'g'], [4, 5, 6, 7], [True, False, True, False])]

For each item in lll, I would like a list of the ith element in each of the items list, and I want all these lists in one list.

This is very tricky to describe with words as you can imagine, but I would like the final result to be like this

result = [
['a', 1, True],
['b', 2, False].
['c', 3, True],
['d', 4, True],
['e', 5, False],
['f', 6, True],
['g', 7, False]
]

What I tried so far is

newList = []
for item in lll:
    num_things = len(item[0])
    for ii in range(num_things):
        newList.append([x[ii] for x in item])

newList

Is there a more pythonically elegant way to get this result?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
SantoshGupta7
  • 5,607
  • 14
  • 58
  • 116

4 Answers4

3

Your original code isn't bad at all. These existing answers are great, but I think

result = []

for item in lll:
    result.extend(zip(*item))

is reasonably succinct and Pythonic, arguably more readable than a one-liner, no imports and worth consideration. extend is a common refactor for append + loop for flattening lists and zip is almost always the way to go for columnwise iteration and matrix rotation.

If you need lists instead of tuples:

result = []

for item in lll:
    result.extend([list(x) for x in zip(*item)])

or with map:

for item in lll:
    result.extend(map(list, zip(*item)))

A couple of minor style suggestions courtesy of PEP-8:

  • snake_case is preferred over camelCase.
  • Avoid l as a variable name. It's too easily confused for i and 1.

Also, num_things = len(item[0]) strikes me as a bit dangerous because it'll raise an IndexError if item is empty.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
3

Simple one-liner with no imports:

[[*ys] for xss in lll for ys in zip(*xss)]

The zip-star transposes, the second for concatenates, and the inner square brackets convert the tuples from the zip to lists.

gilch
  • 10,813
  • 1
  • 23
  • 28
  • @ggorlen the difference is that your version builds a list of tuples, while mine builds the requested list of lists. Different types. – gilch Feb 09 '21 at 06:46
  • Ah, good call. Thanks for clarifying. You even wrote that in the explanation :-) – ggorlen Feb 09 '21 at 06:46
2

Use zip + chain.from_iterable

from itertools import chain

o = chain.from_iterable(zip(*ll) for ll in lll)
print(list(o))

# output
# [('a', 1, True), ('b', 2, False), ('c', 3, True), ('d', 4, True), ('e', 5, False), ('f', 6, True), ('g', 7, False)]

You can then map each element to list using list comprehension asyou want list of lists and not list of tuples.

l = [list(i) for i in o]
print(l)

# output
# [['a', 1, True], ['b', 2, False], ['c', 3, True], ['d', 4, True], ['e', 5, False], ['f', 6, True], ['g', 7, False]]

AnkurSaxena
  • 825
  • 7
  • 11
0

I believe you want this:

list(map(list,zip(*[sum(l,[]) for l in zip(*lll)])))
Bobby Ocean
  • 3,120
  • 1
  • 8
  • 15
  • 1
    That's an abuse of `sum` which is intended only for numeric types. That it currently works on lists in CPython is an implementation detail that is not guaranteed to hold for other Python implementations or future versions. See `help(sum)`. – gilch Feb 09 '21 at 06:21
  • What, I have never heard of that? The sum() function is intended to have a manipulatable initial value. This is what allows one to use sum() on any class with an __add__(). – Bobby Ocean Feb 09 '21 at 06:26
  • `sum`'s docstring says "This function is intended specifically for use with numeric values and may reject non-numeric types." – gilch Feb 09 '21 at 06:29
  • an example would be helpful? – Bobby Ocean Feb 09 '21 at 06:38
  • It's not the same as a reduce with add, even in CPython. Compare `reduce(add, "abc", "")`, which works, to `sum("abc", "")`, which is an error. – gilch Feb 09 '21 at 06:39
  • but that is becuase "" is immutable and has nothing to do with sum(). do you have an example where sum() fails to work? – Bobby Ocean Feb 09 '21 at 06:40
  • Even if it does work forever, it should be rejected as too inefficient because it hits up the memory allocator for each and every item, creating a list object and throwing it away immediately. – ggorlen Feb 09 '21 at 06:41
  • 1
    What? Immutability has nothing to do with it. The default `0` is quite immutable, you know. – gilch Feb 09 '21 at 06:42
  • Obviously 0 is special. I am simply not falling your point. I have used sum() commonly in this way and never had an issue. I have even seen it in cookbooks. In any case, you still haven't provided an example. – Bobby Ocean Feb 09 '21 at 06:47
  • `0` is not special! Addition is not generally a mutation. It returns a result, not a mutated operand. There are lots of immutable objects you can use `+` on, including strings, tuples, and numbers. `sum("abc", "")` *is* my example of `sum` rejecting a non-numeric type. – gilch Feb 09 '21 at 06:59
  • I don't know why you are freaking out about this one point. sum() can be used with other stuff, get over it. I agree that the reason it doesn't work is probably not because of immutability, my bad. however it is clearly a special exception for a string that even pops up with a reminder to use the ''.join() function. do you have an example that is not an explicit reminder that another builtin already exists? ALSO, 0 is very special because there is ONLY one 0 ever; in fact there is only one of each of the numbers -5 all the way to 256 in hard coded in the kernel. – Bobby Ocean Feb 09 '21 at 07:10