39

I have a tuple of tuples - for example:

tupleOfTuples = ((1, 2), (3, 4), (5,))

I want to convert this into a flat, one-dimensional list of all the elements in order:

[1, 2, 3, 4, 5]

I've been trying to accomplish this with list comprehension. But I can't seem to figure it out. I was able to accomplish it with a for-each loop:

myList = []
for tuple in tupleOfTuples:
   myList = myList + list(tuple)

But I feel like there must be a way to do this with a list comprehension.

A simple [list(tuple) for tuple in tupleOfTuples] just gives you a list of lists, instead of individual elements. I thought I could perhaps build on this by using the unpacking operator to then unpack the list, like so:

[*list(tuple) for tuple in tupleOfTuples]

or

[*(list(tuple)) for tuple in tupleOfTuples]

... but that didn't work. Any ideas? Or should I just stick to the loop?

froadie
  • 79,995
  • 75
  • 166
  • 235

7 Answers7

76

it's typically referred to as flattening a nested structure.

>>> tupleOfTuples = ((1, 2), (3, 4), (5,))
>>> [element for tupl in tupleOfTuples for element in tupl]
[1, 2, 3, 4, 5]

Just to demonstrate efficiency:

>>> import timeit
>>> it = lambda: list(chain(*tupleOfTuples))
>>> timeit.timeit(it)
2.1475738355700913
>>> lc = lambda: [element for tupl in tupleOfTuples for element in tupl]
>>> timeit.timeit(lc)
1.5745135182887857

ETA: Please don't use tuple as a variable name, it shadows built-in.

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
  • thanks... do you mind giving i and j more meaningful names so I can more easily follow this logic? – froadie Jul 08 '10 at 13:57
  • @SilentGhost - thanks! is this widely accepted? I find it harder to understand at first glance than the longer loop... but if programmers recognize the pattern then I would use it – froadie Jul 08 '10 at 14:01
  • 1
    @froadie: it's an idiomatic way of flattening a shallow list. – SilentGhost Jul 08 '10 at 14:15
  • I wondered how it looks for longer lists, ie `tupleOfTuples=tuple(zip(range(0,100,2), range(1,100,2)))` -- in that case, `chain` is slightly faster than the LC (and Fabian's `chain.from_iterable` is the fastest) – Jochen Ritzel Jul 08 '10 at 15:05
  • @THC4k: Not on my machine, here fastest one is `list(chain.from_iterable(tupleOfTuples))`. Fabians, solution is as slow as mine or even slower. – SilentGhost Jul 08 '10 at 15:17
  • @SilentGhost yeah you're right, i actually tested that same code, not Fabians LC. – Jochen Ritzel Jul 08 '10 at 16:17
  • What about `numpy.array(tupleOfTuples).flatten()` ? After making it a list – Euler_Salter Jun 11 '18 at 12:18
  • 3
    Struggling to understand how it works. Is there any resource you can recommend with a step-by-step explanation of it? – Sadderdaze Oct 15 '19 at 15:36
46

Just use sum if you don't have a lot of tuples.

>>> tupleOfTuples = ((1, 2), (3, 4), (5,))
>>> sum(tupleOfTuples, ())
(1, 2, 3, 4, 5)
>>> list(sum(tupleOfTuples, ())) # if you really need a list
[1, 2, 3, 4, 5]

If you do have a lot of tuples, use list comprehension or chain.from_iterable to prevent the quadratic behavior of sum.


Micro-benchmarks:

  • Python 2.6

    • Long tuple of short tuples

      $ python2.6 -m timeit -s 'tot = ((1, 2), )*500' '[element for tupl in tot for element in tupl]'
      10000 loops, best of 3: 134 usec per loop
      $ python2.6 -m timeit -s 'tot = ((1, 2), )*500' 'list(sum(tot, ()))'
      1000 loops, best of 3: 1.1 msec per loop
      $ python2.6 -m timeit -s 'tot = ((1, 2), )*500; from itertools import chain; ci = chain.from_iterable' 'list(ci(tot))'
      10000 loops, best of 3: 60.1 usec per loop
      $ python2.6 -m timeit -s 'tot = ((1, 2), )*500; from itertools import chain' 'list(chain(*tot))'
      10000 loops, best of 3: 64.8 usec per loop
      
    • Short tuple of long tuples

      $ python2.6 -m timeit -s 'tot = ((1, )*500, (2, )*500)' '[element for tupl in tot for element in tupl]'
      10000 loops, best of 3: 65.6 usec per loop
      $ python2.6 -m timeit -s 'tot = ((1, )*500, (2, )*500)' 'list(sum(tot, ()))'
      100000 loops, best of 3: 16.9 usec per loop
      $ python2.6 -m timeit -s 'tot = ((1, )*500, (2, )*500); from itertools import chain; ci = chain.from_iterable' 'list(ci(tot))'
      10000 loops, best of 3: 25.8 usec per loop
      $ python2.6 -m timeit -s 'tot = ((1, )*500, (2, )*500); from itertools import chain' 'list(chain(*tot))'
      10000 loops, best of 3: 26.5 usec per loop
      
  • Python 3.1

    • Long tuple of short tuples

      $ python3.1 -m timeit -s 'tot = ((1, 2), )*500' '[element for tupl in tot for element in tupl]'
      10000 loops, best of 3: 121 usec per loop
      $ python3.1 -m timeit -s 'tot = ((1, 2), )*500' 'list(sum(tot, ()))'
      1000 loops, best of 3: 1.09 msec per loop
      $ python3.1 -m timeit -s 'tot = ((1, 2), )*500; from itertools import chain; ci = chain.from_iterable' 'list(ci(tot))'
      10000 loops, best of 3: 59.5 usec per loop
      $ python3.1 -m timeit -s 'tot = ((1, 2), )*500; from itertools import chain' 'list(chain(*tot))'
      10000 loops, best of 3: 63.2 usec per loop
      
    • Short tuple of long tuples

      $ python3.1 -m timeit -s 'tot = ((1, )*500, (2, )*500)' '[element for tupl in tot for element in tupl]'
      10000 loops, best of 3: 66.1 usec per loop
      $ python3.1 -m timeit -s 'tot = ((1, )*500, (2, )*500)' 'list(sum(tot, ()))'
      100000 loops, best of 3: 16.3 usec per loop
      $ python3.1 -m timeit -s 'tot = ((1, )*500, (2, )*500); from itertools import chain; ci = chain.from_iterable' 'list(ci(tot))'
      10000 loops, best of 3: 25.4 usec per loop
      $ python3.1 -m timeit -s 'tot = ((1, )*500, (2, )*500); from itertools import chain' 'list(chain(*tot))'
      10000 loops, best of 3: 25.6 usec per loop
      

Observation:

  • sum is faster if the outer tuple is short.
  • list(chain.from_iterable(x)) is faster if the outer tuple is long.
Community
  • 1
  • 1
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 1
    Wow... wouldn't have thought. For others who are also surprised note that if `b = ((1, 2,3), (4, 5), (5,6,7,8))` then `sum(b)` is `(1, 2, 3, 4, 5, 5, 6, 7, 8)`.... and if `c = [(1, 2,3), (4, 5), (5,6,7,8)]` then `sum(c)` is `(1, 2, 3, 4, 5, 5, 6, 7, 8)`. I think that this is just extremely useful to know! – pandita Sep 06 '13 at 14:36
  • This only works for tuples that have summable data types. – ZSG Feb 09 '16 at 06:11
  • This doesn't work if there's a chance `tupleOfTuples` doesn't contain all tuples :) – Qix - MONICA WAS MISTREATED Jun 16 '16 at 05:49
13

You're chaining the tuples together:

from itertools import chain
print list(chain(*listOfTuples))

Should be pretty readable if you're familiar with itertools, and without the explicit list you even have your result in generator form.

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
9

I like using 'reduce' in this situation (this is what reduce made for!)

lot = ((1, 2), (3, 4), (5,))
print list(reduce(lambda t1, t2: t1 + t2, lot))

 > [1,2,3,4,5]
Donald Miner
  • 38,889
  • 8
  • 95
  • 118
9

Most of these answers will only work for a single level of flattening. For a more comprehensive solution, try this (from http://rightfootin.blogspot.com/2006/09/more-on-python-flatten.html):

def flatten(l, ltypes=(list, tuple)):
    ltype = type(l)
    l = list(l)
    i = 0
    while i < len(l):
        while isinstance(l[i], ltypes):
            if not l[i]:
                l.pop(i)
                i -= 1
                break
            else:
                l[i:i + 1] = l[i]
        i += 1
    return ltype(l)
Craig Trader
  • 15,507
  • 6
  • 37
  • 55
4

Another solution using itertools.chain

>>> tupleOfTuples = ((1, 2), (3, 4), (5,))
>>> from itertools import chain
>>> [x for x in chain.from_iterable(tupleOfTuples)]
[1, 2, 3, 4, 5]
Mad Scientist
  • 18,090
  • 12
  • 83
  • 109
4

For multilevel, and readable code:

def flatten(bla):
    output = []
    for item in bla:
        output += flatten(item) if hasattr (item, "__iter__") or hasattr (item, "__len__") else [item]
    return output

I could not get this to fit in one line (and remain readable, even by far)

jsbueno
  • 99,910
  • 10
  • 151
  • 209