Как сделать плоский список из списка списков?

Интересно, есть ли ярлык для создания простого списка из списка списков в Python.

Я могу сделать это в цикле for, но, может быть, есть какой-нибудь крутой "однострочный"? Я пробовал с помощью Reduce, но я получаю ошибку.

Код

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)

Сообщение об ошибке

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'

88 ответов

Решение

Приведен список списков l,

flat_list = [item for sublist in l for item in sublist]

что значит:

for sublist in l:
    for item in sublist:
        flat_list.append(item)

быстрее, чем ярлыки, опубликованные до сих пор. (l это список, чтобы сгладить.)

Вот соответствующая функция:

flatten = lambda l: [item for sublist in l for item in sublist]

В качестве доказательства вы можете использовать timeit Модуль в стандартной библиотеке:

$ python -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 3: 143 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 1.1 msec per loop

Пояснение: ярлыки на основе + (включая подразумеваемое использование в sum) по необходимости, O(L**2) когда есть L подсписков - поскольку список промежуточных результатов становится длиннее, на каждом шаге выделяется новый объект списка промежуточных результатов, и все элементы в предыдущем промежуточном результате должны быть скопированы (а также добавлено несколько новых). в конце). Итак, для простоты и без фактической потери общности, скажем, у вас есть L подсписков из I элементов каждый: первые I элементы копируются туда и обратно L-1 раз, вторые I элементы L-2 раза и т. Д.; общее количество копий равно I умноженной на сумму x для x от 1 до L, т.е. I * (L**2)/2,

Понимание списка только генерирует один список, один раз, и копирует каждый элемент (из его первоначального места жительства в список результатов) также ровно один раз.

Ты можешь использовать itertools.chain():

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

или, на Python >=2.6, используйте itertools.chain.from_iterable() который не требует распаковки списка:

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

Этот подход, возможно, более читабелен, чем [item for sublist in l for item in sublist] и, кажется, тоже быстрее:

[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
10000 loops, best of 3: 24.2 usec per loop
[me@home]$ python -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 3: 45.2 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 488 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 522 usec per loop
[me@home]$ python --version
Python 2.7.3

Примечание автора: это неэффективно. Но весело, потому что моноиды потрясающие. Это не подходит для производственного кода Python.

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

Это просто суммирует элементы итерируемого, переданного в первом аргументе, рассматривая второй аргумент как начальное значение суммы (если не задано, 0 используется вместо этого, и этот случай даст вам ошибку).

Поскольку вы суммируете вложенные списки, вы на самом деле получаете [1,3]+[2,4] как результат sum([[1,3],[2,4]],[]), который равен [1,3,2,4],

Обратите внимание, что работает только со списками списков. Для списков списков списков вам понадобится другое решение.

Я тестировал большинство предлагаемых решений с помощью perfplot ( мой любимый проект, по сути, обертка вокруг timeit), и нашел

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

быть самым быстрым решением. (operator.iadd одинаково быстро.)


Код для воспроизведения сюжета:

import functools
import itertools
import numpy
import operator
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(numpy.array(a).flat)


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


perfplot.show(
    setup=lambda n: [list(range(10))] * n,
    kernels=[
        forfor, sum_brackets, functools_reduce, functools_reduce_iconcat,
        itertools_chain, numpy_flat, numpy_concatenate
        ],
    n_range=[2**k for k in range(16)],
    logx=True,
    logy=True,
    xlabel='num lists'
    )
from functools import reduce #python 3

>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(lambda x,y: x+y,l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

extend() метод в вашем примере изменяет x вместо возврата полезного значения (которое reduce() надеется).

Более быстрый способ сделать reduce версия будет

>>> import operator
>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(operator.concat, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Не изобретайте велосипед, если вы используете Django:

>>> from django.contrib.admin.utils import flatten
>>> l = [[1,2,3], [4,5], [6]]
>>> flatten(l)
>>> [1, 2, 3, 4, 5, 6]

...Панды:

>>> from pandas.core.common import flatten
>>> list(flatten(l))

...Itertools:

>>> import itertools
>>> flatten = itertools.chain.from_iterable
>>> list(flatten(l))

...Матплотлиб

>>> from matplotlib.cbook import flatten
>>> list(flatten(l))

...Unipath:

>>> from unipath.path import flatten
>>> list(flatten(l))

...Setuptools:

>>> from setuptools.namespaces import flatten
>>> list(flatten(l))

Вот общий подход, который применяется к числам, строкам, вложенным спискам и смешанным контейнерам.

Код

from collections import Iterable


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

Примечание: в Python 3 yield from flatten(x) может заменить for sub_x in flatten(x): yield sub_x

демонстрация

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

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

Ссылка

  • Это решение модифицировано по рецепту Бизли, Д. и Б. Джонса. Рецепт 4.14, поваренная книга Python, 3-е издание, O'Reilly Media Inc., Севастополь, Калифорния: 2013.
  • Нашел более ранний пост SO, возможно, оригинальную демонстрацию.

Если вы хотите сгладить структуру данных, в которой вы не знаете, как глубоко она вложена, вы можете использовать iteration_utilities.deepflatten 1

>>> 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]

Это генератор, поэтому вам нужно привести результат к list или явно перебрать его.


Чтобы сгладить только один уровень, и если каждый из элементов сам по себе итеративен, вы также можете использовать iteration_utilities.flatten которая сама по себе является просто тонкой оберткой вокруг 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]

Просто добавим немного времени (на основе ответа Нико Шлёмера, который не включает функцию, представленную в этом ответе):

Это логарифмический сюжет для огромного диапазона значений. Для качественного рассуждения: чем ниже, тем лучше.

Результаты показывают, что если итерируемый содержит только несколько внутренних итераций, то sum будет самым быстрым, однако для длинных итераций только itertools.chain.from_iterable, iteration_utilities.deepflatten или вложенное понимание имеет разумную производительность с itertools.chain.from_iterable Быть самым быстрым (как уже заметил Нико Шлёмер).

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 Отказ от ответственности: я автор этой библиотеки

Следующее кажется мне самым простым:

>>> 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]

Рассмотрите возможность установки more_itertools пакет.

> pip install more_itertools

Он поставляется с реализацией для flatten( источник, из рецептов itertools):

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]

Начиная с версии 2.4, вы можете выровнять более сложные вложенные итерации с помощью more_itertools.collapse ( источник предоставлен 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]

Я забираю свое заявление обратно. сумма не победитель. Хотя это быстрее, когда список маленький. Но производительность значительно ухудшается с большими списками.

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10000'
    ).timeit(100)
2.0440959930419922

Суммарная версия все еще работает более минуты, и она еще не обработана!

Для средних списков:

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
20.126545906066895
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
22.242258071899414
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
16.449732065200806

Используя маленькие списки и timeit: number=1000000

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
2.4598159790039062
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.5289170742034912
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.0598428249359131

Там, кажется, путаница с operator.add! Когда вы добавляете два списка вместе, правильный термин для этого concat, не добавить. operator.concat это то, что вам нужно использовать.

Если вы думаете, функционально, это так просто, как это:

>>> list2d = ((1, 2, 3), (4, 5, 6), (7,), (8, 9))
>>> reduce(operator.concat, list2d)
(1, 2, 3, 4, 5, 6, 7, 8, 9)

Вы видите, что Reduced уважает тип последовательности, поэтому, когда вы предоставляете кортеж, вы получаете обратно кортеж. давайте попробуем со списком::

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> reduce(operator.concat, list2d)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Ага, вы получите список обратно.

Как насчет производительности::

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> %timeit list(itertools.chain.from_iterable(list2d))
1000000 loops, best of 3: 1.36 µs per loop

from_iterable довольно быстрый! Но это не сравнить, чтобы уменьшить с Конкат.

>>> list2d = ((1, 2, 3),(4, 5, 6), (7,), (8, 9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop

Почему вы используете расширение?

reduce(lambda x, y: x+y, l)

Это должно работать нормально.

Причина, по которой ваша функция не сработала: расширение расширяет массив на месте и не возвращает его. Вы все еще можете вернуть x из лямбды, используя некоторые хитрости:

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

Примечание: расширение более эффективно, чем + в списках.

По вашему списку [[1, 2, 3], [4, 5, 6], [7], [8, 9]]который является 1 уровнем списка, мы можем просто использовать sum(list,[])без использования каких-либо библиотек

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

matplotlib.cbook.flatten() будет работать для вложенных списков, даже если они вложены глубже, чем в примере.

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))

Результат:

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

Это в 18 раз быстрее, чем подчеркивание._. 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

Рекурсивная версия

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

def flatten_list(k):
    result = list()
    for i in k:
        if isinstance(i,list):

            #The isinstance() function checks if the object (first argument) is an 
            #instance or subclass of classinfo class (second argument)

            result.extend(flatten_list(i)) #Recursive call
        else:
            result.append(i)
    return result

flatten_list(x)
#result = [1,2,3,4,5,6,7,8,9,10]
def flatten(l, a):
    for i in l:
        if isinstance(i, list):
            flatten(i, a)
        else:
            a.append(i)
    return a

print(flatten([[[1, [1,1, [3, [4,5,]]]], 2, 3], [4, 5],6], []))

# [1, 1, 1, 3, 4, 5, 2, 3, 4, 5, 6]

Принятый ответ не работал для меня при работе с текстовыми списками переменной длины. Вот альтернативный подход, который работал для меня.

l = ['aaa', 'bb', 'cccccc', ['xx', 'yyyyyyy']]

Принимается ответ, который не сработал:

flat_list = [item for sublist in l for item in sublist]
print(flat_list)
['a', 'a', 'a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'c', 'xx', 'yyyyyyy']

Новое предлагаемое решение, которое сработало для меня:

flat_list = []
_ = [flat_list.extend(item) if isinstance(item, list) else flat_list.append(item) for item in l if item]
print(flat_list)
['aaa', 'bb', 'cccccc', 'xx', 'yyyyyyy']

Можно также использовать квартиру NumPy:

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

Редактировать 02.11.2016: Работает только в том случае, если подсписки имеют одинаковые размеры.

Плохая особенность функции Anil, описанной выше, состоит в том, что она требует, чтобы пользователь всегда вручную указывал второй аргумент, чтобы он был пустым списком. [], Вместо этого это должно быть по умолчанию. Из-за того, как работают объекты Python, они должны быть установлены внутри функции, а не в аргументах.

Вот рабочая функция:

def list_flatten(l, a=None):
    #check a
    if a is None:
        #initialize with empty list
        a = []

    for i in l:
        if isinstance(i, list):
            list_flatten(i, a)
        else:
            a.append(i)
    return a

Тестирование:

In [2]: lst = [1, 2, [3], [[4]],[5,[6]]]

In [3]: lst
Out[3]: [1, 2, [3], [[4]], [5, [6]]]

In [11]: list_flatten(lst)
Out[11]: [1, 2, 3, 4, 5, 6]

Вы можете использовать list extend метод, он оказывается самым быстрым:

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

спектакль:

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',
    )

выход:

Используйте два forв понимании списка:

      l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
flat_l = [e for v in l for e in v]
print(flat_l)

Есть несколько ответов с такой же схемой рекурсивного добавления, как показано ниже, но ни один из них не использует try, что делает решение более надежным и питоническим. Другие преимущества этого решения:

  • работает с любыми итерациями (даже с будущими)
  • работает с любым сочетанием и глубиной вложенности
  • нет зависимостей

~

def flatten(itr):
    t = tuple()
    for e in itr:
        try:
            t += flatten(e)
        except:
            t += (e,)
    return t

Мне лично сложно запомнить все модули, которые нужно было импортировать. Поэтому я предпочитаю использовать простой метод, хотя я не знаю, как его производительность сравнивается с другими ответами.

from collections.abc import Iterable
def flatten(lst):
    for item in lst:
        if isinstance(item, Iterable) and not isinstance(item, str):
            yield from flatten(item)
        else:
            yield item
test case:
          a =[0, [], "fun", [1, 2, 3], [4, 5, 6], 3, [7], [8, 9]]
          list(flatten(a))
output 
          [0, 'fun', 1, 2, 3, 4, 5, 6, 3, 7, 8, 9]

Примечание: ниже относится к Python 3.3+, потому что он использует yield_from, six это также сторонний пакет, хотя и стабильный. Вы также можете использовать sys.version,


В случае obj = [[1, 2,], [3, 4], [5, 6]] все решения здесь хороши, включая понимание списка и itertools.chain.from_iterable,

Однако рассмотрим этот чуть более сложный случай:

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

Здесь есть несколько проблем:

  • Один элемент, 6, это просто скаляр; это не повторяется, поэтому приведенные выше маршруты потерпят неудачу.
  • Один элемент, 'abc', технически повторяем (все str с). Однако, читая немного между строк, вы не хотите рассматривать это как таковое - вы хотите рассматривать это как отдельный элемент.
  • Последний элемент, [8, [9, 10]] само по себе является вложенным итеративным. Базовое понимание списка и chain.from_iterable только извлечь "1 уровень вниз".

Вы можете исправить это следующим образом:

>>> 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]

Здесь вы проверяете, что подэлемент (1) итерируется с Iterable Азбука от itertools, но также хочу убедиться, что (2) элемент не является "строковым".

Если вы готовы отказаться от небольшого количества скорости для более чистого вида, то вы можете использовать numpy.concatenate().tolist() или же 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

Вы можете узнать больше здесь в документах numpy.concatenate и numpy.ravel

Это игра с исходным кодом плаката. (Он был не за горами)

      f = []
list(map(f.extend, l))
from nltk import flatten

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

Преимущество этого решения перед большинством других здесь заключается в том, что если у вас есть список вроде:

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

в то время как большинство других решений выдают ошибку, это решение их обрабатывает.

Вы можете использовать NumPy:
flat_list = list(np.concatenate(list_of_list))

Другие вопросы по тегам