How do I make a flat list out of a list of lists?
Asked Answered
R

33

5325

I have a list of lists like

[
    [1, 2, 3],
    [4, 5, 6],
    [7],
    [8, 9]
]

How can I flatten it to get [1, 2, 3, 4, 5, 6, 7, 8, 9]?


If your list of lists comes from a nested list comprehension, the problem can be solved more simply/directly by fixing the comprehension; please see How can I get a flat result from a list comprehension instead of a nested list?.

The most popular solutions here generally only flatten one "level" of the nested list. See Flatten an irregular (arbitrarily nested) list of lists for solutions that completely flatten a deeply nested structure (recursively, in general).

Rattlesnake answered 4/6, 2009 at 20:30 Comment(1)
There's an in-depth discussion of this here: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html, discussing several methods of flattening arbitrarily nested lists of lists. An interesting read!Pocketknife
C
7306

A list of lists named xss can be flattened using a nested list comprehension:

flat_list = [
    x
    for xs in xss
    for x in xs
]

The above is equivalent to:

flat_list = []

for xs in xss:
    for x in xs:
        flat_list.append(x)

Here is the corresponding function:

def flatten(xss):
    return [x for xs in xss for x in xs]

This is the fastest method. As evidence, using the timeit module in the standard library, we see:

$ python -mtimeit -s'xss=[[1,2,3],[4,5,6],[7],[8,9]]*99' '[x for xs in xss for x in xs]'
10000 loops, best of 3: 143 usec per loop

$ python -mtimeit -s'xss=[[1,2,3],[4,5,6],[7],[8,9]]*99' 'sum(xss, [])'
1000 loops, best of 3: 969 usec per loop

$ python -mtimeit -s'xss=[[1,2,3],[4,5,6],[7],[8,9]]*99' 'reduce(lambda xs, ys: xs + ys, xss)'
1000 loops, best of 3: 1.1 msec per loop

Explanation: the methods based on + (including the implied use in sum) are, of necessity, O(L**2) when there are L sublists -- as the intermediate result list keeps getting longer, at each step a new intermediate result list object gets allocated, and all the items in the previous intermediate result must be copied over (as well as a few new ones added at the end). So, for simplicity and without actual loss of generality, say you have L sublists of M items each: the first M items are copied back and forth L-1 times, the second M items L-2 times, and so on; total number of copies is M times the sum of x for x from 1 to L excluded, i.e., M * (L**2)/2.

The list comprehension just generates one list, once, and copies each item over (from its original place of residence to the result list) also exactly once.

Combination answered 4/6, 2009 at 20:37 Comment(10)
I tried a test with the same data, using itertools.chain.from_iterable : $ python -mtimeit -s'from itertools import chain; l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'list(chain.from_iterable(l))'. It runs a bit more than twice as fast as the nested list comprehension that's the fastest of the alternatives shown here.Burlie
I found the syntax hard to understand until I realized you can think of it exactly like nested for loops. for sublist in l: for item in sublist: yield itemAlys
[leaf for tree in forest for leaf in tree] might be easier to comprehend and apply.Bores
@RobCrowell Same here. To me the list comprehension one doesn't read right, something feels off about it - I always seem to get it wrong and end up googling. To me this reads right [leaf for leaf in tree for tree in forest]. I wish this is how it was. I am sure I am missing something about the grammar here, and I would appreciate if anyone could point that out.Congregation
I kept looking here every time I wanted to flatten a list, but this gif is what drove it home: i.stack.imgur.com/0GoV5.gifDenn
@Sнаđошƒаӽ I have the same feeling, but think about it like this : variables have to be defined before they are accessible. In [leaf for leaf in tree for tree in forest], tree does not exist yet when first accessed with in. It is only defined by for tree in forest. Therefore one needs to write [leaf for tree in forest for leaf in tree]. Yes, I know, this doesn’t apply to the first leaf, though… (Also I’m not saying it’s how things actually work inside.)Heffner
@Sнаđошƒаӽ it makes sense if you consider a list comprehension as a flattened series of for loops. Ignore the first leaf, as this is just the item you want to populate the list with, and then look at the series of for loops, which would be nested as: line 1: for tree in forest:, line 2: for leaf in tree:. If you just append the second for loop to the first, you get the syntax of a list comprehension.Eastereasterday
The solution fails if your list elements are strings, as they are flattened to single chars too...Backstay
The way I like to think of it for memorization purposes is: [a for b in c for a in b].Phillipphillipe
Coming from Javascript, list comprehension continues to trip me up. I'd expect the leaf to be at the end, or maybe even for the fors to be nested sorta like .map()s would be in JS, e.g. for tree in forest (for leaf in tree (leaf * 2)). Not sure what the best answer is, but by the looks of the upvotes on these other comments, the syntax they chose was a mistake.Portraitist
B
2375

You can use itertools.chain():

>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain(*list2d))

Or you can use itertools.chain.from_iterable() which doesn't require unpacking the list with the * operator:

>>> import itertools
>>> list2d = [[1,2,3], [4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain.from_iterable(list2d))

This approach is arguably more readable than [item for sublist in l for item in sublist] and appears to be faster too:

$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
20000 loops, best of 5: 10.8 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 5: 21.7 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 5: 258 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;from functools import reduce' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 5: 292 usec per loop
$ python3 --version
Python 3.7.5rc1
Bone answered 4/6, 2009 at 21:6 Comment(5)
The * is the tricky thing that makes chain less straightforward than the list comprehension. You have to know that chain only joins together the iterables passed as parameters, and the * causes the top-level list to be expanded into parameters, so chain joins together all those iterables, but doesn't descend further. I think this makes the comprehension more readable than the use of chain in this case.Larcher
@TimDierks: I'm not sure "this requires you to understand Python syntax" is an argument against using a given technique in Python. Sure, complex usage could confuse, but the "splat" operator is generally useful in many circumstances, and this isn't using it in a particularly obscure way; rejecting all language features that aren't necessarily obvious to beginning users means you're tying one hand behind your back. May as well throw out list comprehensions too while you're at it; users from other backgrounds would find a for loop that repeatedly appends more obvious.Cyperaceous
* creates an intermediary tuple.! from_iterable fetch the nested lists directly from the top list.Vague
To make this more readable, you can make a simple function: def flatten_list(deep_list: list[list[object]]): return list(chain.from_iterable(deep_list)). The type hinting improves the clarity of what's going on (modern IDEs would interpret this as returning a list[object] type).Bangka
Even shorter is [*chain(*l)] (python3.5+, released 2015)Cuvette
M
1388

Note from the author: This is very inefficient. But fun, because monoids are awesome.

>>> xss = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> sum(xss, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

sum sums the elements of the iterable xss, and uses the second argument as the initial value [] for the sum. (The default initial value is 0, which is not a list.)

Because you are summing nested lists, you actually get [1,3]+[2,4] as a result of sum([[1,3],[2,4]],[]), which is equal to [1,3,2,4].

Note that only works on lists of lists. For lists of lists of lists, you'll need another solution.

Mintamintage answered 4/6, 2009 at 20:35 Comment(5)
that's pretty neat and clever but I wouldn't use it because it's confusing to read.Macrobiotics
This is a Shlemiel the painter's algorithm joelonsoftware.com/articles/fog0000000319.html -- unnecessarily inefficient as well as unnecessarily ugly.Circumambulate
The append operation on lists forms a Monoid, which is one of the most convenient abstractions for thinking of a + operation in a general sense (not limited to numbers only). So this answer deserves a +1 from me for (correct) treatment of lists as a monoid. The performance is concerning though...Puryear
this is a very inefficient way because of the quadratic aspect of the sum.Trumpeter
This article explains the maths of the inefficiency mathieularose.com/how-not-to-flatten-a-list-of-lists-in-pythonMalang
A
897

I tested most suggested solutions with perfplot (a pet project of mine, essentially a wrapper around timeit), and found

import functools
import operator
functools.reduce(operator.iconcat, a, [])

to be the fastest solution, both when many small lists and few long lists are concatenated. (operator.iadd is equally fast.)

A simpler and also acceptable variant is

out = []
for sublist in a:
    out.extend(sublist)

If the number of sublists is large, this performs a little worse than the above suggestion.

enter image description here

enter image description here


Code to reproduce the plot:

import functools
import itertools
import operator

import numpy as np
import perfplot


def forfor(a):
    return [item for sublist in a for item in sublist]


def sum_brackets(a):
    return sum(a, [])


def functools_reduce(a):
    return functools.reduce(operator.concat, a)


def functools_reduce_iconcat(a):
    return functools.reduce(operator.iconcat, a, [])


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(np.array(a).flat)


def numpy_concatenate(a):
    return list(np.concatenate(a))


def extend(a):
    out = []
    for sublist in a:
        out.extend(sublist)
    return out


b = perfplot.bench(
    setup=lambda n: [list(range(10))] * n,
    # setup=lambda n: [list(range(n))] * 10,
    kernels=[
        forfor,
        sum_brackets,
        functools_reduce,
        functools_reduce_iconcat,
        itertools_chain,
        numpy_flat,
        numpy_concatenate,
        extend,
    ],
    n_range=[2 ** k for k in range(16)],
    xlabel="num lists (of length 10)",
    # xlabel="len lists (10 lists total)"
)
b.save("out.png")
b.show()
Adlai answered 26/7, 2017 at 9:38 Comment(8)
For huge nested lists,' list(numpy.array(a).flat)' is the fastest among all functions above.Plashy
Is there a way to do a 3-d perfplot? number of arrays by average size of array?Hexane
@Plashy can you define "huge" please?Depreciatory
Tried numpy_flat on the test example from Rossetta Code (link) and got VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarrayBillye
One option missed above which shows up faster for my particular case i just items = []; for sublist in a: items.extend(sublist); return sublistUnclog
@Plashy the solution fails in case the length of the elements list is not the same. For example: [[1, 2], [3, 4], [5]]Picofarad
For different sublist lengths, np.array flat doesn't work. E.g., a = [ [1,2], [1,2,3]] list(np.array(a).flat) will return the original list. It's safer to use list(np.concatenate(a))Eft
out += sublist is faster than out.extend(sublist) (in the first plot, with many short lists). See iadd vs extend.Chiropody
J
335

Using functools.reduce, which adds an accumulated list xs to the next list ys:

from functools import reduce
xss = [[1,2,3], [4,5,6], [7], [8,9]]
out = reduce(lambda xs, ys: xs + ys, xss)

Output:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

A faster way using operator.concat:

from functools import reduce
import operator
xss = [[1,2,3], [4,5,6], [7], [8,9]]
out = reduce(operator.concat, xss)

Output:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
Jag answered 4/6, 2009 at 20:35 Comment(3)
This is a Shlemiel the painter's algorithm joelonsoftware.com/articles/fog0000000319.htmlCircumambulate
reduce is very inefficient for this use case as it will repeat copies and generate many unused temporary lists (O(n^2) in both time and possibly space, depending on how the GC decides to clean). It's better to use append or extend.Overreach
this fails if either inner or outer list is emptyApocope
D
198

Here is a general approach that applies to objects (e.g. numbers, strings) in nested and mixed containers. This can flatten both simple and complicated containers (see also Demo).

Code

from typing import Iterable 
#from collections import Iterable                            # < py38


def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

Notes:

  • In Python 3, yield from flatten(x) can replace for sub_x in flatten(x): yield sub_x
  • In Python 3.8, abstract base classes are moved from collection.abc to the typing module.

Demo

simple = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(simple))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

complicated = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"]              # numbers, strs, nested & mixed
list(flatten(complicated))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']

Reference

  • This solution is modified from a recipe in Beazley, D. and B. Jones. Recipe 4.14, Python Cookbook 3rd Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Found an earlier SO post, possibly the original demonstration.
Davena answered 29/11, 2016 at 4:14 Comment(7)
I just wrote pretty much the same, because I didn't see your solution ... here is what I looked for "recursively flatten complete multiple lists" ... (+1)Hydrophobia
@MartinThoma Much appreciated. FYI, if flattening nested iterables is a common practice for you, there are some third-party packages that handle this well. This may save from reinventing the wheel. I've mentioned more_itertools among others discussed in this post. Cheers.Davena
You can check if hasattr(x, '__iter__') instead of importing/checking against Iterable and that will exclude strings as well.Veiling
the above code doesnt seem to work for if one of the nested lists is having a list of strings. [1, 2, [3, 4], [4], [], 9, 9.5, 'ssssss', ['str', 'sss', 'ss'], [3, 4, 5]] output:- [1, 2, 3, 4, 4, 9, 9.5, 'ssssss', 3, 4, 5]Microbe
@Microbe It seems to work when I try your input, even with a deeply nested list of strings, e.g. list(flatten([["a", "b", ["c", "d", ["e", "f", ["g"]]]]])) -> ['a', 'b', 'c', 'd', 'e', 'f', 'g']. What version of Python are you using?Davena
Strings are a PITA : they act like lists when we do NOT want them to . so yes this code is neededManassas
@Davena While this is a great answer and works, it seems confusing to some readers that you introduce this as that applies to numbers, strings, nested lists and mixed containers because they expect that strings are split into chars. What about clarifying to: that applies to numbers and strings in (nested) lists and mixed containers? BTW: it also flattens objects.Valedictorian
B
122

To flatten a data-structure that is deeply nested, use iteration_utilities.deepflatten1:

>>> from iteration_utilities import deepflatten

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(deepflatten(l, depth=1))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> l = [[1, 2, 3], [4, [5, 6]], 7, [8, 9]]
>>> list(deepflatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

It's a generator so you need to cast the result to a list or explicitly iterate over it.


To flatten only one level and if each of the items is itself iterable you can also use iteration_utilities.flatten which itself is just a thin wrapper around itertools.chain.from_iterable:

>>> from iteration_utilities import flatten
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(flatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Just to add some timings (based on Nico Schlömer's answer that didn't include the function presented in this answer):

Enter image description here

It's a log-log plot to accommodate for the huge range of values spanned. For qualitative reasoning: Lower is better.

The results show that if the iterable contains only a few inner iterables then sum will be fastest, however for long iterables only the itertools.chain.from_iterable, iteration_utilities.deepflatten or the nested comprehension have reasonable performance with itertools.chain.from_iterable being the fastest (as already noticed by Nico Schlömer).

from itertools import chain
from functools import reduce
from collections import Iterable  # or from collections.abc import Iterable
import operator
from iteration_utilities import deepflatten

def nested_list_comprehension(lsts):
    return [item for sublist in lsts for item in sublist]

def itertools_chain_from_iterable(lsts):
    return list(chain.from_iterable(lsts))

def pythons_sum(lsts):
    return sum(lsts, [])

def reduce_add(lsts):
    return reduce(lambda x, y: x + y, lsts)

def pylangs_flatten(lsts):
    return list(flatten(lsts))

def flatten(items):
    """Yield items from any nested iterable; see REF."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            yield from flatten(x)
        else:
            yield x

def reduce_concat(lsts):
    return reduce(operator.concat, lsts)

def iteration_utilities_deepflatten(lsts):
    return list(deepflatten(lsts, depth=1))


from simple_benchmark import benchmark

b = benchmark(
    [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add,
     pylangs_flatten, reduce_concat, iteration_utilities_deepflatten],
    arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)},
    argument_name='number of inner lists'
)

b.plot()

1 Disclaimer: I'm the author of that library

Blackguard answered 26/11, 2016 at 0:20 Comment(0)
H
54

The following seems simplest to me:

>>> import numpy as np
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> print(np.concatenate(l))
[1 2 3 4 5 6 7 8 9]
Handhold answered 5/7, 2017 at 5:14 Comment(1)
OP doesn't mention they want to use numpy. Python has good ways of doing this without relying on a libraryLoveinidleness
D
51

Consider installing the more_itertools package.

> pip install more_itertools

It ships with an implementation for flatten (source, from the itertools recipes):

import more_itertools


lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.flatten(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Note: as mentioned in the docs, flatten requires a list of lists. See below on flattening more irregular inputs.


As of version 2.4, you can flatten more complicated, nested iterables with more_itertools.collapse (source, contributed by abarnet).

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.collapse(lst)) 
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

lst = [[1, 2, 3], [[4, 5, 6]], [[[7]]], 8, 9]              # complex nesting
list(more_itertools.collapse(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Davena answered 2/12, 2016 at 18:35 Comment(4)
If you can afford adding a package to your project - this answer is bestMachine
it fails when all elements are not list. (e.g. lst=[1, [2,3]]). of course integer is not iterable.Leffert
also, mind that list of strings will be flattened to a list of charactersMachine
I'd reverse the answer, emphasizing collapse over flatten (leaving it for special case of pure lists of lists).Billye
A
36

The reason your function didn't work is because the extend extends an array in-place and doesn't return it. You can still return x from lambda, using something like this:

reduce(lambda x,y: x.extend(y) or x, l)

Note: extend is more efficient than + on lists.

Arevalo answered 4/6, 2009 at 20:47 Comment(2)
extend is better used as newlist = [], extend = newlist.extend, for sublist in l: extend(l) as it avoids the (rather large) overhead of the lambda, the attribute lookup on x, and the or.Valora
for python 3 add from functools import reduceCheckerwork
A
36

According your list [[1, 2, 3], [4, 5, 6], [7], [8, 9]] which is 1 list level, we can simply use sum(list,[]) without using any libraries

sum([[1, 2, 3], [4, 5, 6], [7], [8, 9]],[])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

To extend the advantage of this method when there is a tuple or number existing inside. Simply adding a mapping function for each element by map to the list

#For only tuple
sum(list(map(list,[[1, 2, 3], (4, 5, 6), (7,), [8, 9]])),[])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

#In general

def convert(x):
    if type(x) is int or type(x) is float:
           return [x]
    else:
           return list(x)

sum(list(map(convert,[[1, 2, 3], (4, 5, 6), 7, [8, 9]])),[])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

In here, there is a clear explanation of the drawback in terms of memory for this approach. In short, it recursively creates list objects, which should be avoided :(

Antiphrasis answered 9/12, 2021 at 9:15 Comment(3)
This answer is already up in this question: https://mcmap.net/q/36065/-how-do-i-make-a-flat-list-out-of-a-list-of-listsMealworm
Neat! Though the other answer here, https://mcmap.net/q/36065/-how-do-i-make-a-flat-list-out-of-a-list-of-lists, explains the reasons this solution should generally be avoided (it's inefficient and confusing.)Shela
Will also give a TypeError if your list contains a tuplePtyalism
D
29

matplotlib.cbook.flatten() will work for nested lists even if they nest more deeply than the example.

import matplotlib
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
print(list(matplotlib.cbook.flatten(l)))
l2 = [[1, 2, 3], [4, 5, 6], [7], [8, [9, 10, [11, 12, [13]]]]]
print(list(matplotlib.cbook.flatten(l2)))

Result:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

This is 18x faster than underscore._.flatten:

Average time over 1000 trials of matplotlib.cbook.flatten: 2.55e-05 sec
Average time over 1000 trials of underscore._.flatten: 4.63e-04 sec
(time for underscore._)/(time for matplotlib.cbook) = 18.1233394636
Double answered 1/2, 2018 at 18:22 Comment(0)
F
22

One can also use NumPy's flat:

import numpy as np
list(np.array(l).flat)

It only works when sublists have identical dimensions.

Fatness answered 17/7, 2016 at 12:57 Comment(0)
J
14

You can use the list extend method. It shows to be the fastest:

flat_list = []
for sublist in l:
    flat_list.extend(sublist)

Performance:

import functools
import itertools
import numpy
import operator
import perfplot


def functools_reduce_iconcat(a):
    return functools.reduce(operator.iconcat, a, [])


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


def extend(a):
    n = []

    list(map(n.extend, a))

    return n


perfplot.show(
    setup = lambda n: [list(range(10))] * n,
    kernels = [
        functools_reduce_iconcat, extend, itertools_chain, numpy_flat
        ],
    n_range = [2**k for k in range(16)],
    xlabel = 'num lists',
    )

Output:

Enter image description here

Jaban answered 25/1, 2020 at 21:8 Comment(0)
C
12

There are several answers with the same recursive appending scheme as below, but none makes use of try, which makes the solution more robust and Pythonic.

def flatten(itr):
    for x in itr:
        try:
            yield from flatten(x)
        except TypeError:
            yield x

Usage: this is a generator, and you typically want to enclose it in an iterable builder like list() or tuple() or use it in a for loop.

Advantages of this solution are:

  • works with any kind of iterable (even future ones!)
  • works with any combination and deepness of nesting
  • works also if top level contains bare items
  • no dependencies
  • fast and efficient (you can flatten the nested iterable partially, without wasting time on the remaining part you don't need)
  • versatile (you can use it to build an iterable of your choice or in a loop)

N.B.: Since all iterables are flattened, strings are decomposed into sequences of single characters. If you don't like/want such behavior, you can use the following version which filters out from flattening iterables like strings and bytes:

def flatten(itr):
    if type(itr) in (str,bytes):
        yield itr
    else:
        for x in itr:
            try:
                yield from flatten(x)
            except TypeError:
                yield x
Capital answered 8/8, 2020 at 14:52 Comment(2)
Wouldn't it be marginally faster if the tuple of types was a hash set?Iseult
@VladimirVilimaitis As stated in answer, the code returns a generator, you can use it directly or create with it a sequence whose type is unrelated to the flattening process, it depends on your needs.Capital
T
10

Note: Below applies to Python 3.3+ because it uses yield_from. six is also a third-party package, though it is stable. Alternately, you could use sys.version.


In the case of obj = [[1, 2,], [3, 4], [5, 6]], all of the solutions here are good, including list comprehension and itertools.chain.from_iterable.

However, consider this slightly more complex case:

>>> obj = [[1, 2, 3], [4, 5], 6, 'abc', [7], [8, [9, 10]]]

There are several problems here:

  • One element, 6, is just a scalar; it's not iterable, so the above routes will fail here.
  • One element, 'abc', is technically iterable (all strs are). However, reading between the lines a bit, you don't want to treat it as such--you want to treat it as a single element.
  • The final element, [8, [9, 10]] is itself a nested iterable. Basic list comprehension and chain.from_iterable only extract "1 level down."

You can remedy this as follows:

>>> from collections import Iterable
>>> from six import string_types

>>> def flatten(obj):
...     for i in obj:
...         if isinstance(i, Iterable) and not isinstance(i, string_types):
...             yield from flatten(i)
...         else:
...             yield i


>>> list(flatten(obj))
[1, 2, 3, 4, 5, 6, 'abc', 7, 8, 9, 10]

Here, you check that the sub-element (1) is iterable with Iterable, an ABC from itertools, but also want to ensure that (2) the element is not "string-like."

Tillman answered 1/2, 2018 at 18:33 Comment(1)
If you are still interested in Python 2 compatibility, change yield from to a for loop, e.g. for x in flatten(i): yield xDavena
O
8

If you are willing to give up a tiny amount of speed for a cleaner look, then you could use numpy.concatenate().tolist() or numpy.concatenate().ravel().tolist():

import numpy

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] * 99

%timeit numpy.concatenate(l).ravel().tolist()
1000 loops, best of 3: 313 µs per loop

%timeit numpy.concatenate(l).tolist()
1000 loops, best of 3: 312 µs per loop

%timeit [item for sublist in l for item in sublist]
1000 loops, best of 3: 31.5 µs per loop

You can find out more here in the documentation, numpy.concatenate and numpy.ravel.

Ophiology answered 27/10, 2016 at 3:24 Comment(2)
Doesn't work for unevenly nested lists like [1, 2, [3], [[4]], [5, [6]]]Double
@juanpa.arrivillaga it's a simple and natural extension of the question, though. Answers that can handle greater depth of nesting are more likely to be useful to someone who finds this question.Double
W
5
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])
Wheelchair answered 8/8, 2017 at 14:59 Comment(1)
Fails for python2.7 for the example nested list in the question: [[1, 2, 3], [4, 5, 6], [7], [8, 9]]Double
I
4

This may not be the most efficient way, but I thought to put a one-liner (actually a two-liner). Both versions will work on arbitrary hierarchy nested lists, and exploits language features (Python 3.5) and recursion.

def make_list_flat (l):
    flist = []
    flist.extend ([l]) if (type (l) is not list) else [flist.extend (make_list_flat (e)) for e in l]
    return flist

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

The output is

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

This works in a depth first manner. The recursion goes down until it finds a non-list element, then extends the local variable flist and then rolls back it to the parent. Whenever flist is returned, it is extended to the parent's flist in the list comprehension. Therefore, at the root, a flat list is returned.

The above one creates several local lists and returns them which are used to extend the parent's list. I think the way around for this may be creating a gloabl flist, like below.

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = []
def make_list_flat (l):
    flist.extend ([l]) if (type (l) is not list) else [make_list_flat (e) for e in l]

make_list_flat(a)
print (flist)

The output is again

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

Although I am not sure at this time about the efficiency.

Intoxicate answered 16/5, 2018 at 9:41 Comment(1)
Why extend([l]) instead of append(l)?Mascagni
A
4

I wanted a solution which can deal with multiple nesting ([[1], [[[2]], [3]]], [1, 2, 3] for example), but would also not be recursive (I had a big level of recursion and I got a recursion error.

This is what I came up with:

def _flatten(l) -> Iterator[Any]:
    stack = l.copy()
    while stack:
        item = stack.pop()
        if isinstance(item, list):
            stack.extend(item)
        else:
            yield item


def flatten(l) -> Iterator[Any]:
    return reversed(list(_flatten(l)))

and tests:

@pytest.mark.parametrize('input_list, expected_output', [
    ([1, 2, 3], [1, 2, 3]),
    ([[1], 2, 3], [1, 2, 3]),
    ([[1], [2], 3], [1, 2, 3]),
    ([[1], [2], [3]], [1, 2, 3]),
    ([[1], [[2]], [3]], [1, 2, 3]),
    ([[1], [[[2]], [3]]], [1, 2, 3]),
])
def test_flatten(input_list, expected_output):
    assert list(flatten(input_list)) == expected_output
Anselmi answered 7/10, 2021 at 17:38 Comment(0)
C
4

If you want to unnest everything and keep a distinct list of elements, you could use this as well.

list_of_lists = [[1,2], [2,3], [3,4]]
list(set.union(*[set(s) for s in list_of_lists]))
Capel answered 25/7, 2022 at 9:26 Comment(0)
D
3

Here's an approach I didn't see in the other answers. It supports any level of nesting, works iteratively and without libraries:

mylist = [[1,2,4,5],[[0,8,9],5,7],[3,11,[44,45,46],25]]

for i,_ in enumerate(mylist):          # indexes, including extended positions
    while isinstance(mylist[i],list):  # drill down/extend current position
        mylist[i:i+1] = mylist[i]      # as long as item is a list

print(mylist)
[1, 2, 4, 5, 0, 8, 9, 5, 7, 3, 11, 44, 45, 46, 25]
Damper answered 21/3, 2023 at 21:52 Comment(1)
Nice not to use stack, but it has very bad time complexity.Serpentine
S
2

Another unusual approach that works for hetero- and homogeneous lists of integers:

from typing import List


def flatten(l: list) -> List[int]:
    """Flatten an arbitrary deep nested list of lists of integers.

    Examples:
        >>> flatten([1, 2, [1, [10]]])
        [1, 2, 1, 10]

    Args:
        l: Union[l, Union[int, List[int]]

    Returns:
        Flatted list of integer
    """
    return [int(i.strip('[ ]')) for i in str(l).split(',')]
Swetlana answered 9/1, 2018 at 14:34 Comment(6)
That's just a more complicated and a bit slower way of what ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 already posted before. I reinvented his proposal yesterday, so this approach seems quite popular these days ;)Koralle
Not quite: wierd_list = [[1, 2, 3], [4, 5, 6], [7], [8, 9], 10] >> nice_list=[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 0]Swetlana
my code as one liner would be : flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]Swetlana
You are indeed right +1, ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000's proposal won't work with multiple digit numbers, I also didn't test this before although it should be obvious. You could simplify your code and write [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. But I'd suggest to stick with Deleet's proposal for real use cases. It doesn't contain hacky type transformations, it's faster and more versatile because it naturally also handles lists with mixed types.Koralle
Can you tell us which book? I contemplated a lot about this because it's so effective and beautiful. Will hit recursion limit inevitably in general but for cases like this with few recursions it seems perfect.Koralle
Unfortunately no. But I saw this code recently here: Python Practice Book 6.1.2Swetlana
W
2

A non-recursive function to flatten lists of lists of any depth:

def flatten_list(list1):
    out = []
    inside = list1
    while inside:
        x = inside.pop(0)
        if isinstance(x, list):
            inside[0:0] = x
        else:
            out.append(x)
    return out

l = [[[1,2],3,[4,[[5,6],7],[8]]],[9,10,11]]
flatten_list(l)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Woo answered 9/12, 2021 at 6:10 Comment(0)
T
2

Not a one-liner, but seeing all the answers here, I guess this long list missed some pattern matching, so here it is :)

The two methods are probably not efficient, but anyway, it's easy to read (to me at least; perhaps I'm spoiled by functional programming):

def flat(x):
    match x:
        case []:
            return []
        case [[*sublist], *r]:
            return [*sublist, *flat(r)]

The second version considers lists of lists of lists... whatever the nesting:

def flat(x):
    match x:
        case []:
            return []
        case [[*sublist], *r]:
            return [*flat(sublist), *flat(r)]
        case [h, *r]:
            return [h, *flat(r)]
Tutu answered 14/3, 2022 at 13:6 Comment(0)
O
2

If you have a numpy array a:

a = np.array([[1,2], [3,4]])
a.flatten('C')

produces:

[1, 2, 3, 4]

np.flatten also accepts other parameters:

  • C:
  • F
  • A
  • K

More details about parameters are available here.

Outmost answered 14/10, 2022 at 13:19 Comment(1)
This answer only works for rectangular array (matrix) structures and not when the lengths of contained lists are not equal.Microvolt
T
0

If I want to add something to the great previous answers, here is my recursive flatten function which can flatten not only nested lists, but also any given container or any generally any object which can throw out items. This does also work for any depth of nesting and it is a lazy iterator which yields the items as requested:

def flatten(iterable):
    # These types won't considered a sequence or generally a container
    exclude = str, bytes

    for i in iterable:
        try:
            if isinstance(i, exclude):
                raise TypeError
            iter(i)
        except TypeError:
            yield i
        else:
            yield from flatten(i)

This way, you can exclude types you don't want to be flattened, like str or what else.

The idea is if an object can pass the iter() it's ready to yield items. So the iterable can have even generator expressions as an item.

Someone could argue: Why did you write this that generic when the OP didn't ask for it? OK, you're right. I just felt like this might help someone (like it did for myself).

Test cases:

lst1 = [1, {3}, (1, 6), [[3, 8]], [[[5]]], 9, ((((2,),),),)]
lst2 = ['3', B'A', [[[(i ** 2 for i in range(3))]]], range(3)]

print(list(flatten(lst1)))
print(list(flatten(lst2)))

Output:

[1, 3, 1, 6, 3, 8, 5, 9, 2]
['3', b'A', 0, 1, 4, 0, 1, 2]
Thompson answered 11/1, 2022 at 15:56 Comment(0)
L
0

I would suggest using generators with yield statement and yield from. Here's an example:

from collections.abc import Iterable

def flatten(items, ignore_types=(bytes, str)):
    """
       Flatten all of the nested lists to the one. Ignoring flatting of iterable types str and bytes by default.
    """
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, ignore_types):
            yield from flatten(x)
        else:
            yield x

values = [7, [4, 3, 5, [7, 3], (3, 4), ('A', {'B', 'C'})]]

for v in flatten(values):
    print(v)
Literati answered 15/5, 2022 at 17:7 Comment(0)
D
0

For a list containing multiple list here a recursive solution that work for me and that i hope is correct:

# Question 4
def flatten(input_ls=[]) -> []:
    res_ls = []
    res_ls = flatten_recursive(input_ls, res_ls)

    print("Final flatten list solution is: \n", res_ls)

    return res_ls


def flatten_recursive(input_ls=[], res_ls=[]) -> []:
    tmp_ls = []

    for i in input_ls:
        if isinstance(i, int):
            res_ls.append(i)
        else:
            tmp_ls = i
            tmp_ls.append(flatten_recursive(i, res_ls))

    print(res_ls)
    return res_ls


flatten([0, 1, [2, 3], 4, [5, 6]])  # test
flatten([0, [[[1]]], [[2, 3], [4, [[5, 6]]]]])

Output:

[0, 1, 2, 3]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6]
Final flatten list solution is: 
 [0, 1, 2, 3, 4, 5, 6]
[0, 1]
[0, 1]
[0, 1]
[0, 1, 2, 3]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6]
Final flatten list solution is: 
 [0, 1, 2, 3, 4, 5, 6]
Dragon answered 10/8, 2022 at 10:5 Comment(0)
T
0

I like to add a high performant generator solution which can fatten nested lists (or any kind of iterable) of any depth not (only 2D-lists):

from itertools import chain

def flatten_deep_generator(iterable):
    iterator = iter(iterable)
    try:
        while 1: # StopIteration will break the loop
            item = next(iterator)
            # check if item contains sub-items
            if not hasattr(item,'__trunc__'):
                iterator = chain(iter(item), iterator)
            else:
                yield item
    except StopIteration:
        pass

Depending on your needs a generators have huge advantages over lists. E.g. If you want add filter() functions afterwards. The resulting list should be instanced only at the end after you have constructed the full generator incl. the filtering by this you avoid multiple iterations over the items.

Remark: Compaired to the other proposed generator solution this is an iterative and not a recursive solution which avoids RecursionErrors in case of deep nested iterables.

Transmissible answered 11/3, 2023 at 21:48 Comment(2)
what is the role of __trunc__ in this "high performant generator solution"? and whyFog
The __trunc__ attribute is used to identify a numerical item or in other words a no more iterable item. It can be that we have some cornercases not covered then you must switch to the slower but more secured way: hastattr(item,'__iter__') or hasattr(item,'__next__') The solution should work on any iterable object so we cannot use a checlk like type(item) is list. E.g. the solution works on nested iterators, tuples and even strings would be flattend to single characters.Transmissible
L
-1

Late to the party, though a recursive solution has not been proposed before, so here you go!

def lift_list(input_list):
    if input_list == []:
        return []
    return lift_list(input_list[0]) + (lift_list(input_list[1:]) if len(input_list) > 1 else []) if isinstance(input_list, list) else [input_list]

Note that this solution works also with subnested lists and singletons:

>>> lift_list([1, 2, [1,2,3], [1,2], [4, [5, [6]]], [3,4]])
... [1, 2, 1, 2, 3, 1, 2, 4, 5, 6, 3, 4]
Lycanthrope answered 10/4 at 19:15 Comment(1)
Would be good to know why the downvoteLycanthrope
S
-2

Considering the list has just integers:

import re
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(map(int,re.sub('(\[|\])','',str(l)).split(',')))
Sternson answered 2/4, 2022 at 15:31 Comment(0)
F
-5

I created a little function which can basically flatten anything. You can get it with pip: pip install flatten-everything

from flatten_everything import flatten_everything
withoutprotection=list(
    flatten_everything(
        [
            1,
            1,
            2,
            [3, 4, 5, [6, 3, [2, 5, ["sfs", "sdfsfdsf",]]]],
            1,
            3,
            34,
            [
                55,
                {"brand": "Ford", "model": "Mustang", "year": 1964, "yearxx": 2020},
                pd.DataFrame({"col1": [1, 2], "col2": [3, 4]}),
                {"col1": [1, 2], "col2": [3, 4]},
                55,
                {"k32", 34},
                np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]),
                (np.arange(22), np.eye(2, 2), 33),
            ],
        ]
    )
)
print(withoutprotection)
output:
[1, 1, 2, 3, 4, 5, 6, 3, 2, 5, 'sfs', 'sdfsfdsf', 1, 3, 34, 55, 'Ford', 'Mustang', 1964, 2020, 1, 2, 3, 4, 1, 2, 3, 4, 55, 34, 'k32', 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 1.0, 0.0, 0.0, 1.0, 33]

You can even protect objects from getting flattened:

from flatten_everything import ProtectedDict,ProtectedList,ProtectedTuple
withprotection=list(
    flatten_everything(
        [
            1,
            1,
            2,
            [3, 4, 5, [6, 3, [2, 5, ProtectedList(["sfs", "sdfsfdsf",])]]],
            1,
            3,
            34,
            [
                55,
                ProtectedDict({"brand": "Ford", "model": "Mustang", "year": 1964, "yearxx": 2020}),
                pd.DataFrame({"col1": [1, 2], "col2": [3, 4]}),
                {"col1": [1, 2], "col2": [3, 4]},
                55,
                {"k32", 34},
                np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]),
                ProtectedTuple((np.arange(22), np.eye(2, 2), 33)),
            ],
        ]
    )
)
print(withprotection)
output:
[1, 1, 2, 3, 4, 5, 6, 3, 2, 5, ['sfs', 'sdfsfdsf'], 1, 3, 34, 55, {'brand': 'Ford', 'model': 'Mustang', 'year': 1964, 'yearxx': 2020}, 1, 2, 3, 4, 1, 2, 3, 4, 55, 34, 'k32', 1, 2, 3, 4, 5, 6, 7, 8, (array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,17, 18, 19, 20, 21]), array([[1., 0.], [0., 1.]]), 33)]
Feltner answered 1/9, 2022 at 22:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.