Як зробити плоский список зі списку списків?


3368

Цікаво, чи є ярлик, щоб зробити простий список зі списку списків на 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
Деякі інші відповіді є кращими, але причина, через яку ваш не вдається, полягає в тому, що метод "розширення" завжди повертає "Жоден". Для списку довжиною 2 він працюватиме, але повертає None. Для більш тривалого списку він буде споживати перші 2 аргументи, які повертають None. Потім це продовжується з None.extend (<третій аргумент>), що спричинює цю
помилку

@ рішення шон-підборіддя тут є більш пітонічним, але якщо вам потрібно зберегти тип послідовності, скажімо, у вас є кортеж кортежів, а не список списків, тоді вам слід скористатися скороченням (operator.concat, tuple_of_tuples). Використання operator.concat з кортежами, здається, працює швидше, ніж ланцюжок.from_iterables зі списком.
Мейтхем

Відповіді:


4789

Враховуючи список списків 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 разів перевищує суму x для x від 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
Синтаксису мені було важко зрозуміти, поки я не зрозумів, що ти можеш думати про нього саме як вкладений для циклів. для підсистеми в л: для елемента в підспісі: елемент доходу
Роб Кроуелл

23
@BorisChervenkov: Зауважте, що я завершив дзвінок, list()щоб перетворити ітератор у список.
інтуїтив

163
[лист для дерева в лісі для листя в дереві] може бути легше зрозуміти і застосувати.
Джон Мей

80
@Joel, насправді в наш час list(itertools.chain.from_iterable(l))найкраще - як помічено в інших коментарях та відповіді Шона.
Алекс Мартеллі

1566

Ви можете використовувати 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

906

Примітка автора : Це неефективно. Але весело, бо моноїди - приголомшливі. Це не підходить для виробництва 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].

Зауважте, що працює лише у списках списків. Для списків списків списків вам знадобиться інше рішення.


99
це дуже акуратно і розумно, але я б не користувався цим, оскільки читати його заплутано.
andrewrk

87
Це алгоритм художника Шлейміля joelonsoftware.com/articles/fog0000000319.html - надмірно неефективний, а також надмірно потворний.
Майк Грем

44
Операція додавання у списках формує a Monoid, що є однією з найзручніших абстракцій для мислення +операції в загальному розумінні (не обмежується лише числами). Тож ця відповідь заслуговує +1 від мене за (правильне) трактування списків як моноїда. Виступ стосується хоча ...
улідко

7
@andrewrk Ну, деякі люди думають, що це найчистіший спосіб зробити це: youtube.com/watch?v=IOiZatlZtGU тим, хто не розуміє, чому це круто, просто потрібно почекати кілька десятиліть, поки всі не зроблять це так: ) давайте використовувати мови програмування (і абстракції), які виявлені і не винайдені, Monoid виявлений.
jhegedus

11
це дуже неефективний спосіб через квадратичний аспект суми.
Жан-Франсуа Фабре

459

Я протестував більшість запропонованих рішень з 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
Для величезних вкладених списків "список (numpy.array (a) .flat)" є найшвидшим серед усіх функцій вище.
Сара

Спробували за допомогою регулярного вираження: 'list (map (int, re.findall (r "[\ w] +", str (a)))) ". Швидкість трохи менша, ніж numpy_concatenate
Justas

Чи є спосіб зробити 3-денний перфлот? кількість масивів за середнім розміром масиву?
Лев

Я люблю ваше рішення. Короткий, простий та ефективний :-)
ShadyMBA

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

19
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Функція працює однаково добре для списків цілих чисел та списків рядків.
Грег Хьюгілл

119

Не вигадуйте колесо, якщо використовуєте 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))

... Налаштування :

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

4
flatten = itertools.chain.from_iterableмає бути правильною відповіддю
geckos

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 , можливо, оригінальну демонстрацію.

5
Я просто так само написав, тому що не бачив вашого рішення ... ось що я шукав "рекурсивно сплющувати повний список" ... (+1)
Мартін Тома,

3
@MartinThoma Дуже вдячний. FYI, якщо вирівнювання вкладених ітерабелів є звичайною практикою для вас, є деякі сторонні пакети, які добре справляються з цим. Це може врятувати від винаходи колеса. Я згадував 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 Відмова: Я автор цієї бібліотеки


sumбільше не працює на довільних послідовностях, як це починається 0, роблячи functools.reduce(operator.add, sequences)заміну (хіба ми не раді, що вони були видалені reduceз вбудованих?). Коли типи відомі, можливо, їх швидше використовувати type.__add__.
Янна Верньє

@YannVernier Дякую за інформацію. Я думав, що я застосував ці орієнтири на Python 3.6, і він працював з ним sum. Чи знаєте ви, на яких версіях Python він перестав працювати?
MSeifert

Я дещо помилявся. 0це лише початкове значення за замовчуванням, тому воно працює, якщо використовувати аргумент start для запуску із порожнього списку ... але він все ще містить рядки спеціальних випадків і каже мені використовувати приєднання. Це реалізація 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: число = 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 підлісти, можливо - але оскільки продуктивність суми відповідає O (N ** 2), тоді як розуміння списку йде з O (N), просто зростаючий список вхідних даних трохи змінить речі - - дійсно ЖК буде "нескінченно швидшим", ніж сума на межі в міру зростання N. Я відповідав за розробку суми та її першу реалізацію під час виконання 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)

Ви бачите, що скорочення поважає тип послідовності, тож коли ви постачаєте кортеж, ви отримуєте назад кортеж. Спробуємо зі списком ::

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

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
andorov

Вибачте, що насправді повільно дивіться інші відповіді
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]

Справді. Це має бути прийнята відповідь
брунеттон

Якщо ви можете дозволити додати пакет до свого проекту - найкраще ця відповідь
viddik13

22

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

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

для python 3 addfrom functools import reduce
Markus

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 = [][...]
Poik

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
приємно, не потрібен імпорт, і зрозуміло, що він робить ... вирівнювання списку, період :)
Goran B.

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 разів швидше, ніж підкреслення ._. Сплющуйте:

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

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

13

Погана особливість функції Аніла вище полягає в тому, що вона вимагає від користувача завжди вручну вказувати, що другий аргумент є порожнім списком []. Натомість це має бути за замовчуванням. Завдяки тому, як працюють об’єкти 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]

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.02.2016: Працює лише тоді, коли сублісти мають однакові розміри.


це було б оптимальне рішення?
RetroCode

6

Ви можете використовувати numpy:
flat_list = list(np.concatenate(list_of_list))


Це працює і для числових, рядкових та змішаних списків
Нітін,

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


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

Готово! Звичайно, ви можете повернути його до списку, виконавши список (l)


1
Це неправильно, вирівнювання зменшить розміри 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

pip install underscore.py

Аналогічно можна використовувати pydash . Мені здається, що ця версія набагато легше читається, ніж розуміння списку чи будь-які інші відповіді.
gliemezis

2
Це дуже повільно.
Nico Schlömer

2
Чому у нього є модуль з іменем _? Це здається поганим ім'ям. Див stackoverflow.com/a/5893946/6605826
EL_DON

2
@EL_DON: З сторінки читання underscore.py "Underscore.py - це пітонний порт відмінної бібліотеки 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:])

Помилка 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) є ітерабельним Iterable, з ABC від 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]]

в той час як більшість інших рішень помиляються, це рішення обробляє їх.


У запитанні зазначено "список списків", але ваш приклад містить елемент, який не є списком. Більшість інших рішень дотримуються оригінального питання. Ваше рішення вирішує більш широку проблему, але воно також потребує неосновного пакету Python (nltk), який потрібно встановити спочатку.
simonobo

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 в розумінні списку. Тому в корені повертається плоский список.

Вищенаведений створює кілька локальних списків та повертає їх, які використовуються для розширення списку батьків. Я думаю, що навпаки для цього може бути створення глобулу 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]

Хоча наразі я не впевнений у ефективності.


Навіщо продовжувати ([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(',')]

Це просто складніший і трохи повільніший спосіб того, що вже було розміщено ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000. Я вчора
придумав

Не зовсім так: 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]
Тарндт

мій код як одного лайнера буде: 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 щодо справ реального використання. Він не містить перетворених типів перетворень, він швидший і універсальніший, оскільки він, природно, також обробляє списки зі змішаними типами.
Дарконаут

2
На жаль, немає. Але цей код я нещодавно побачив тут:
Підручник з
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.