Как сделать плоский список из списка списков - PullRequest
2641 голосов
/ 05 июня 2009

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

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

Код

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'

Ответы [ 39 ]

3834 голосов
/ 05 июня 2009

Дан список списков l,

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

, что означает:

flat_list = []
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 подсписков - поскольку список промежуточных результатов продолжает увеличиваться, на каждом шаге a новый объект списка промежуточных результатов распределяется, и все элементы в предыдущем промежуточном результате должны быть скопированы (а также несколько новых, добавленных в конце). Итак, для простоты и без фактической потери общности, скажем, у вас есть L подсписков из I элементов каждый: первые I элементы копируются туда и обратно L-1 раз, вторые I элементы L-2 раза и т. Д .; общее количество копий равно I, умноженному на сумму x для x от 1 до L, т.е. I * (L**2)/2.

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

1295 голосов
/ 05 июня 2009

Вы можете использовать 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
755 голосов
/ 05 июня 2009

Примечание автора : Это неэффективно. Но весело, потому что моноиды потрясающие. Это не подходит для производственного кода 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].

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

279 голосов
/ 26 июля 2017

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

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

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

enter image description here


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

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'
    )
136 голосов
/ 05 июня 2009
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]
79 голосов
/ 29 ноября 2016

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

код

#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

Примечания :

Демо

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.
  • Нашел более раннюю ТАК сообщение , возможно, оригинальную демонстрацию.
39 голосов
/ 26 ноября 2016

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

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

enter image description here

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

Результаты показывают, что если итерируемое содержит только несколько внутренних итераций, то 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 Отказ от ответственности: я являюсь автором этой библиотеки

37 голосов
/ 05 июня 2009

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

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

Использование небольших списков и времени: число = 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
31 голосов
/ 14 сентября 2016

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

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

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

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

>>> 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 довольно быстро! Но это не сравнить, чтобы уменьшить с concat.

>>> list2d = ((1, 2, 3),(4, 5, 6), (7,), (8, 9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop
30 голосов
/ 05 июня 2009

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

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

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

...