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

3380

Интересно, есть ли ярлык для создания простого списка из списка списков в 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'
Эмма
источник
20
Здесь подробно обсуждается это: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , где обсуждаются несколько методов сглаживания произвольно вложенных списков списков. Интересное чтение!
RichieHindle
6
Некоторые другие ответы лучше, но причина, по которой вы терпите неудачу, состоит в том, что метод extension всегда возвращает None. Для списка длиной 2 он будет работать, но вернет None. Для более длинного списка он будет использовать первые 2 аргумента, которые возвращают None. Затем он продолжается с None.extend (<третий аргумент>), что вызывает эту
ошибку
Решение @ shawn-chin здесь более питонное, но если вам нужно сохранить тип последовательности, скажем, у вас есть кортеж кортежей, а не список списков, то вам следует использовать redu (operator.concat, tuple_of_tuples). Использование operator.concat с кортежами работает быстрее, чем chain.from_iterables со списком.
Meitham

Ответы:

4801

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

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

Алекс Мартелли
источник
486
Я попробовал тест с теми же данными, используя 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))'. Он работает чуть более чем в два раза быстрее, чем понимание вложенного списка, что является самой быстрой из представленных здесь альтернатив.
интуитивно
274
Мне было трудно понять синтаксис, пока я не понял, что вы можете думать о нем точно так же, как о вложенных циклах. для подсписка в l: для элемента в подсписке: yield item
Роб Кроуэлл
23
@BorisChervenkov: обратите внимание, что я завернул вызов, list()чтобы реализовать итератор в списке.
интуитивно
163
[лист для дерева в лесу для листа в дереве] может быть легче понять и применить.
Джон Ми
80
@ Джоэл, на самом деле в наши дни list(itertools.chain.from_iterable(l))это лучше всего - как отмечено в других комментариях и ответе Шона.
Алекс Мартелли
1571

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

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

Или вы можете использовать, itertools.chain.from_iterable()который не требует распаковки списка с *оператором :

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain.from_iterable(list2d))
Шон Чин
источник
13
Это *сложная вещь, которая делает chainменее простой, чем понимание списка. Вы должны знать, что цепочка объединяет только итерируемые элементы, передаваемые в качестве параметров, и * заставляет список верхнего уровня расширяться до параметров, поэтому chainобъединяет все эти итерируемые элементы, но не опускается дальше. Я думаю, что это делает понимание более читабельным, чем использование цепочки в этом случае.
Тим Диркс
52
@TimDierks: я не уверен, что «это требует от вас понимания синтаксиса Python» - это аргумент против использования данной техники в Python. Конечно, сложное использование может сбить с толку, но оператор «splat», как правило, полезен во многих обстоятельствах, и это не использует его особенно неясным образом; Отказ от всех языковых функций, которые не обязательно очевидны для начинающих пользователей, означает, что вы связываете одну руку за спиной. Можно также выкинуть списочные понимания, пока вы на нем; пользователи из других областей находят forпетлю, которая становится appendвсе более очевидной.
ShadowRanger
Этот ответ и другие ответы здесь дают неверный результат, если верхний уровень также содержит значение. например, list = [["abc","bcd"],["cde","def"],"efg"]приведет к выводу["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr
Кажется, *оператор не может быть использован в Python2
Wkm
908

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

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

Триптих
источник
100
это довольно опрятно и умно, но я бы не стал его использовать, потому что читать смущает.
andrewrk
87
Это алгоритм художника Шлемеля joelonsoftware.com/articles/fog0000000319.html - излишне неэффективный и безобразно уродливый.
Майк Грэм
44
Операция добавления в списки формирует a Monoid, которая является одной из наиболее удобных абстракций для представления +операции в общем смысле (не ограничиваясь только числами). Так что этот ответ заслуживает от меня +1 за (правильную) обработку списков как моноидов. Спектакль касается, хотя ...
ulidtko
7
@andrewrk Ну, некоторые люди думают, что это самый чистый способ сделать это: youtube.com/watch?v=IOiZatlZtGU тем, кто не понимает, почему это круто, просто нужно подождать несколько десятилетий, пока все не сделают это так: ) давайте использовать языки программирования (и абстракции), которые открыты и не изобретены, Monoid обнаружен.
Джегедус
11
это очень неэффективный способ из-за квадратичного аспекта суммы.
Жан-Франсуа Фабр
462

Я протестировал большинство предлагаемых решений с помощью 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,
    # setup=lambda n: [list(range(n))] * 10,
    kernels=[
        forfor,
        sum_brackets,
        functools_reduce,
        functools_reduce_iconcat,
        itertools_chain,
        numpy_flat,
        numpy_concatenate,
    ],
    n_range=[2 ** k for k in range(16)],
    xlabel="num lists (of length 10)",
    # xlabel="len lists (10 lists total)"
)
Нико Шлёмер
источник
25
Для огромных вложенных списков «list (numpy.array (a) .flat)» является самым быстрым среди всех функций, перечисленных выше.
Сара
Пробовал с помощью регулярных выражений: 'list (map (int, re.findall (r "[\ w] +", str (a))))'. Скорость немного медленнее, чем numpy_concatenate
Justas
Есть ли способ сделать 3-й перфлот? количество массивов по среднему размеру массива?
Лев
Мне нравится ваше решение. Коротко, просто и эффективно :-)
ShadyMBA
Хороший график, люблю свою работу!
CpILL
182
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]
Грег Хьюгилл
источник
20
reduce(operator.add, l)будет правильным способом сделать reduceверсию. Встроенные модули работают быстрее, чем лямбды.
AGF
3
@agf вот как: * timeit.timeit('reduce(operator.add, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000) 0.017956018447875977 * timeit.timeit('reduce(lambda x, y: x+y, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000) 0.025218963623046875
lukmdo
8
Это алгоритм художника Шлемеля joelonsoftware.com/articles/fog0000000319.html
Майк Грэм,
2
это можно использовать только для integers. Но что, если список содержит string?
Фредди
3
@Freddy: operator.addфункция работает одинаково хорошо как для списков целых чисел, так и для списков строк.
Грег Хьюгилл
121

Не изобретайте велосипед, если вы используете 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))
Макс Малыш
источник
4
flatten = itertools.chain.from_iterableдолжен быть правильный ответ
гекконы
3
отличный ответ! работает также для l = [[[1, 2, 3], [4, 5]], 5] в случае панд
Маркус Дучке
1
Мне нравится решение Pandas. Если у вас есть что - то вроде: list_of_menuitems = [1, 2, [3, [4, 5, [6]]]]это приведет на: [1, 2, 3, 4, 5, 6]. То, что я скучаю, это ровный уровень.
imjoseangel
115

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

Код

#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.
  • Нашел более ранний пост SO , возможно, оригинальную демонстрацию.
pylang
источник
5
Я просто написал примерно то же самое, потому что я не видел вашего решения ... вот что я искал для "рекурсивного сглаживания полных множественных списков" ... (+1)
Мартин Тома
3
@MartinThoma Очень ценится. К вашему сведению, если для вас обычным делом является выравнивание вложенных итераций, есть некоторые сторонние пакеты, которые хорошо справляются с этой задачей. Это может спасти от изобретения колеса. Я упомянул more_itertoolsсреди других обсуждаемых в этом посте. Приветствия.
pylang
Возможно, traverseтакже может быть хорошим названием для этого способа дерева, в то время как я бы оставил его менее универсальным для этого ответа, придерживаясь вложенных списков.
Волк
Вы можете проверить if hasattr(x, '__iter__')вместо импорта / проверки, Iterableи это также исключит строки.
Райан Аллен
приведенный выше код не работает, если в одном из вложенных списков есть список строк. [1, 2, [3, 4], [4], [], 9, 9,5, 'ssssss', ['str', 'sss', 'ss'], [3, 4, 5]] вывод: - [1, 2, 3, 4, 4, 9, 9.5, 'ssssss', 3, 4, 5]
sunnyX
51

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

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

MSeifert
источник
sumбольше не работает на произвольных последовательностей , как это начинается с 0, что делает functools.reduce(operator.add, sequences)замену (не мы рады , что они удалены reduceиз встроенных команд?). Когда типы известны, это может быть быстрее для использования type.__add__.
Ян Вернье,
@YannVernier Спасибо за информацию. Я думал, что я провел эти тесты на Python 3.6, и он работал с sum. Вы случайно не знаете, на каких версиях Python он перестал работать?
MSeifert
Я был несколько ошибочным. 0это просто начальное значение по умолчанию, поэтому оно работает, если кто-либо использует аргумент start для запуска с пустым списком ... но он все еще в особых случаях и говорит мне использовать join. Это реализует foldlвместо foldl1. Та же проблема всплывает в 2.7.
Ян Вернье
39

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

>>> 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
Надя Алрамли
источник
23
для действительно крошечного списка, например, одного с 3-мя подсписками, может быть - но поскольку производительность sum идет с O (N ** 2), в то время как понимание списка идет с O (N), простое увеличение входного списка немного перевернет вещи - - действительно, LC будет «бесконечно быстрее», чем сумма на пределе при увеличении N. Я отвечал за разработку sum и выполнение ее первой реализации во время выполнения Python, и мне все еще хотелось бы найти способ эффективно ограничить ее суммированием чисел (что действительно хорошо получается) и блокировать «привлекательную неприятность», которую он предлагает людям кто хочет "суммировать" списки ;-).
Алекс Мартелли
38

Там, кажется, путаница с 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)

Вы видите, что 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довольно быстро! Но это не с чем сравнивать 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
Meitham
источник
1
Хм, чтобы быть справедливым, второй пример также должен быть в списке (или первый кортеж?)
Mr_and_Mrs_D
2
Использование таких небольших входных данных не очень хорошее сравнение. Для 1000 последовательностей длиной 1000 я получаю 0,037 секунды для list(chain.from_iterable(...))и 2,5 секунды для reduce(concat, ...). Проблема в том, что reduce(concat, ...)имеет квадратичное время выполнения, тогда как chainявляется линейным.
kaya3
33

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

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

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

Андреа Амбу
источник
7
для python3from functools import reduce
андоров
Извините, что очень медленно, посмотрите остальные ответы
Mr_and_Mrs_D
Это, безусловно, самое простое для понимания, но короткое решение, которое работает на Python 2 и 3. Я понимаю, что многие пользователи Python занимаются обработкой данных, где требуется обрабатывать огромные объемы данных и, таким образом, много заботиться о скорости, но когда вы пишут сценарий оболочки и имеют только несколько десятков элементов в нескольких подсписках, тогда это идеально.
Асфанд Кази
27

Рассмотрите возможность установки 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]
pylang
источник
Верно. Это должен быть принятый ответ
brunetton
Если вы можете позволить себе добавить пакет в свой проект - этот ответ лучше всего
viddik13
22

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

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

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

Игорь Кривокон
источник
7
extendлучше использовать в качестве newlist = [], extend = newlist.extend, for sublist in l: extend(l)поскольку он избегает (довольно большой) накладных расходов из lambda, что поиск атрибут x, и or.
AGF
для питона 3 добавитьfrom functools import reduce
Маркус Дучке
17
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]
индиго
источник
def flatten(l, a=None): if a is None: a = [][...]
Пойк
16

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

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]
Саурабх Сингх
источник
1
хорошо, импорт не нужен, и понятно, что он делает ... сглаживание списка, точка :)
Горан Б.
1
просто гениально!
Сачин Шарма
15

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
EL_DON
источник
14

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

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']
user9074332
источник
13

Плохая особенность функции 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]
Deleet
источник
13

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

>>> 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]
дьявол в деталях
источник
Не работает для списков с разными размерами. -1
нуруб
10

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

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

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

MDH
источник
это было бы оптимальным решением?
RetroCode 22.09.16
6

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

А. Аттиа
источник
Это работает для числовых, строковых и смешанных списков
Nitin
2
Сбой для неравномерно вложенных данных, как[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

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

MKULTRA
источник
1
Не работает для неравномерно вложенных списков, таких как[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

Самое быстрое решение, которое я нашел (для большого списка в любом случае):

import numpy as np
#turn list into an array and flatten()
np.array(l).flatten()

Выполнено! Конечно, вы можете превратить его обратно в список, выполнив list (l)

канадец
источник
1
Это неправильно, flatten уменьшит размеры массива nd до одного, но не объединит списки внутри как один.
Андо Джурай
5

Простой код для underscore.pyфанатов пакетов

from underscore import _
_.flatten([[1, 2, 3], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Это решает все проблемы сглаживания (ни один элемент списка или сложное вложение)

from underscore import _
# 1 is none list item
# [2, [3]] is complex nesting
_.flatten([1, [2, [3]], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Вы можете установить underscore.pyс помощью пипа

pip install underscore.py
Ву Ань
источник
Точно так же вы можете использовать pydash . Я считаю, что эта версия гораздо более читабельна, чем понимание списка или любые другие ответы.
Глимезис
2
Это супер медленно.
Нико Шлёмер
2
Почему у него есть модуль с именем _? Это похоже на плохое имя. См stackoverflow.com/a/5893946/6605826
EL_DON
2
@EL_DON: со страницы readme для underscore.py «Underscore.py - это порт на python с превосходной библиотекой javascript underscore.js». Я думаю, что это причина для этого имени. И да, это нехорошее имя для питона
Ву Ань
5
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])
englealuze
источник
Сбой для python2.7 для примера вложенного списка в вопросе:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON
@EL_DON протестирован на python 2.7.5. это работает отлично
englealuze
5

Примечание : ниже применяется к 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) итерируем с IterableABC itertools, но также хотите убедиться, что (2) элемент не является «строковым».

Брэд Соломон
источник
1
Если вы все еще заинтересованы в совместимости с Python 2, перейдите yield fromна forцикл, напримерfor x in flatten(i): yield x
pylang
5
flat_list = []
for i in list_of_list:
    flat_list+=i

Этот Кодекс также работает хорошо, поскольку он просто расширяет список полностью. Хотя это очень похоже, но есть только один цикл. Так что это сложнее, чем добавление 2 для циклов.

Дипак Ядав
источник
5
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]]

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

Alijy
источник
В вопросе указан «список списков», но ваш список примеров содержит элемент, не включенный в список. Большинство других решений придерживаются первоначального вопроса. Ваше решение решает более широкую проблему, но оно также требует не базового пакета Python (nltk), который должен быть установлен первым.
Симонобо
4

Возможно, это не самый эффективный способ, но я подумал о том, чтобы поставить одну линию (на самом деле две линии). Обе версии будут работать с вложенными списками произвольной иерархии и использовать языковые функции (Python3.5) и рекурсию.

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)

Выход

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

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

Вышеупомянутый создает несколько локальных списков и возвращает их, которые используются для расширения списка родителей. Я думаю, что путь к этому может быть создание gloabl flist, как показано ниже.

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)

Выход снова

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

Хотя в настоящее время я не уверен в эффективности.

phoxis
источник
Зачем расширять ([l]) вместо добавления (l)?
Maciek
3

Еще один необычный подход, который работает для гетеро- и однородных списков целых чисел:

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(',')]
tharndt
источник
Это просто более сложный и немного более медленный способ того, что уже было опубликовано 3000 фунтов стерлингов. Вчера я заново изобрел его предложение, так что в наши дни этот подход кажется довольно популярным;)
Darkonaut
Не совсем: 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]
tharndt
мой код как один лайнер будет: flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
tharndt
1
Вы действительно правы +1, предложение ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 не будет работать с многозначными числами, я также не проверял это раньше, хотя это должно быть очевидно. Вы могли бы упростить свой код и писать [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. Но я бы предложил придерживаться предложения Deleet для реальных случаев использования. Он не содержит хакерских преобразований типов, он быстрее и более универсален, потому что он, естественно, также обрабатывает списки со смешанными типами.
Darkonaut
2
К сожалению нет. Но я недавно видел этот код здесь: Книга Практики Python 6.1.2
tharndt