Bagaimana cara membuat daftar datar dari daftar daftar?


3376

Saya bertanya-tanya apakah ada jalan pintas untuk membuat daftar sederhana dari daftar daftar dengan Python.

Saya bisa melakukannya dalam satu forlingkaran, tapi mungkin ada beberapa "satu-liner" yang keren? Saya mencobanya dengan reduce(), tetapi saya mendapatkan kesalahan.

Kode

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

Pesan eror

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
Ada diskusi mendalam tentang hal ini di sini: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , membahas beberapa metode meratakan daftar daftar yang bersarang secara sewenang-wenang. Bacaan yang menarik!
RichieHindle

6
Beberapa jawaban lain lebih baik tetapi alasan Anda gagal adalah bahwa metode 'memperpanjang' selalu mengembalikan Tidak Ada. Untuk daftar dengan panjang 2, itu akan berfungsi tetapi tidak mengembalikan. Untuk daftar yang lebih panjang, ia akan menggunakan 2 arg pertama, yang mengembalikan None. Kemudian dilanjutkan dengan None.extend (<argumen ketiga>), yang menyebabkan erro ini
mehtunguh

@ solusi shawn-chin adalah yang lebih pythonic di sini, tetapi jika Anda perlu mempertahankan jenis urutan, katakan Anda memiliki tuple tuple daripada daftar daftar, maka Anda harus menggunakan mengurangi (operator.concat, tuple_of_tuples). Menggunakan operator.concat dengan tuple tampaknya berkinerja lebih cepat daripada chain.from_iterables dengan daftar.
Meitham

Jawaban:


4797

Diberikan daftar daftar l,

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

yang berarti:

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

lebih cepat daripada pintasan yang diposting sejauh ini. (l adalah daftar untuk diratakan.)

Berikut adalah fungsi yang sesuai:

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

Sebagai bukti, Anda dapat menggunakan timeitmodul di perpustakaan standar:

$ 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

Penjelasan: pintasan berdasarkan +(termasuk penggunaan tersirat dalam sum) adalah, tentu saja, O(L**2)ketika ada L daftar - karena daftar hasil menengah terus bertambah, pada setiap langkah objek daftar hasil menengah baru dialokasikan, dan semua item dalam hasil antara sebelumnya harus disalin (serta beberapa yang baru ditambahkan di akhir). Jadi, untuk kesederhanaan dan tanpa kehilangan generalitas yang sebenarnya, katakan Anda memiliki masing-masing sub daftar L item I: item I pertama disalin bolak-balik L-1 kali, item I kedua L-2 kali, dan seterusnya; jumlah total salinan adalah I kali jumlah x untuk x dari 1 hingga L yang dikecualikan, yaitu I * (L**2)/2,.

Pemahaman daftar hanya menghasilkan satu daftar, sekali, dan menyalin setiap item di atas (dari tempat asalnya ke daftar hasil) juga tepat sekali.


486
Saya mencoba tes dengan data yang sama, menggunakan 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))'. Ini berjalan sedikit lebih dari dua kali lebih cepat dari pemahaman daftar bersarang yang tercepat dari alternatif yang ditampilkan di sini.
intuited

274
Saya menemukan sintaks sulit untuk dipahami sampai saya menyadari Anda dapat memikirkannya persis seperti bersarang untuk loop. untuk sublist di l: untuk item di sublist: yield item
Rob Crowell

23
@ BorisChervenkov: Perhatikan bahwa saya memasukkan panggilan list()untuk mewujudkan iterator ke dalam daftar.
intuited

163
[daun untuk pohon di hutan untuk daun di pohon] mungkin lebih mudah untuk dipahami dan diterapkan.
John Mee

80
@ Joel, sebenarnya saat ini list(itertools.chain.from_iterable(l))adalah yang terbaik - seperti yang diperhatikan dalam komentar lain dan jawaban Shawn.
Alex Martelli

1569

Anda bisa menggunakan itertools.chain():

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

Atau Anda dapat menggunakan itertools.chain.from_iterable()yang tidak memerlukan pembongkaran daftar dengan *operator :

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

13
Ini *adalah hal yang rumit yang membuatnya chainlebih mudah daripada pemahaman daftar. Anda harus tahu bahwa rantai hanya bergabung bersama iterables yang diteruskan sebagai parameter, dan * menyebabkan daftar tingkat atas diperluas menjadi parameter, jadi chainbergabunglah bersama semua iterables itu, tetapi tidak turun lebih jauh. Saya pikir ini membuat pemahaman lebih mudah dibaca daripada penggunaan rantai dalam kasus ini.
Tim Dierks

52
@TimDierks: Saya tidak yakin "ini mengharuskan Anda untuk memahami sintaksis Python" adalah argumen yang menentang penggunaan teknik yang diberikan dalam Python. Tentu, penggunaan yang rumit dapat membingungkan, tetapi operator "percikan" umumnya berguna dalam banyak keadaan, dan ini tidak menggunakannya dengan cara yang sangat tidak jelas; menolak semua fitur bahasa yang belum tentu jelas bagi pengguna pemula berarti Anda mengikat satu tangan di belakang Anda. Mungkin juga membuang pemahaman daftar saat Anda berada di sana; pengguna dari latar belakang lain akan menemukan forlingkaran yang berulang kali appendlebih jelas.
ShadowRanger

Jawaban ini, dan jawaban lain di sini, memberikan hasil yang salah jika level atas juga mengandung nilai. misalnya, list = [["abc","bcd"],["cde","def"],"efg"]akan menghasilkan keluaran["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr

Sepertinya *operator tidak dapat digunakan di python2
wkm

908

Catatan dari penulis : Ini tidak efisien. Tapi menyenangkan, karena monoids mengagumkan. Ini tidak sesuai untuk kode Python produksi.

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

Ini hanya menjumlahkan elemen iterable yang diteruskan dalam argumen pertama, memperlakukan argumen kedua sebagai nilai awal dari jumlah (jika tidak diberikan, 0digunakan sebagai gantinya dan kasus ini akan memberi Anda kesalahan).

Karena Anda menjumlahkan daftar bersarang, Anda benar-benar mendapatkan [1,3]+[2,4]sebagai akibat dari sum([[1,3],[2,4]],[]), yang sama dengan[1,3,2,4] .

Perhatikan bahwa hanya berfungsi pada daftar daftar. Untuk daftar daftar daftar, Anda membutuhkan solusi lain.


100
itu cukup rapi dan pintar tetapi saya tidak akan menggunakannya karena itu membingungkan untuk dibaca.
andrewrk

87
Ini adalah algoritma Shlemiel sang pelukis joelonsoftware.com/articles/fog0000000319.html - tidak efisien dan tidak perlu jelek.
Mike Graham

44
Operasi penambahan pada daftar membentuk a Monoid, yang merupakan salah satu abstraksi yang paling nyaman untuk memikirkan +operasi dalam pengertian umum (tidak terbatas pada angka saja). Jadi jawaban ini layak diberi +1 dari saya untuk perlakuan daftar (yang benar) sebagai monoid. Pertunjukannya memprihatinkan ...
ulidtko

7
@andrewrk Nah, beberapa orang berpikir bahwa ini adalah cara paling bersih untuk melakukannya: youtube.com/watch?v=IOiZatlZtGU orang-orang yang tidak mengerti mengapa ini keren hanya perlu menunggu beberapa dekade sampai semua orang melakukannya dengan cara ini: ) mari kita gunakan bahasa pemrograman (dan abstraksi) yang ditemukan dan tidak ditemukan, Monoid ditemukan.
jhegedus

11
ini adalah cara yang sangat tidak efisien karena aspek kuadratik dari penjumlahan.
Jean-François Fabre

461

Saya menguji sebagian besar solusi yang disarankan dengan perfplot (proyek peliharaan saya, pada dasarnya pembungkus timeit), dan ditemukan

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

menjadi solusi tercepat, baik ketika banyak daftar kecil dan beberapa daftar panjang digabungkan. ( operator.iaddsama cepatnya.)

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini


Kode untuk mereproduksi plot:

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
Untuk daftar bersarang yang sangat besar, 'list (numpy.array (a) .flat)' adalah yang tercepat di antara semua fungsi di atas.
Sara

Mencoba menggunakan regex: 'daftar (peta (int, re.findall (r "[\ w] +", str (a)))))'. Kecepatannya sedikit lebih lambat daripada numpy_concatenate
Justas

Apakah ada cara untuk melakukan perfplot 3-d? jumlah array berdasarkan ukuran rata-rata array?
Leo

Saya suka solusi Anda. Singkat: sederhana, dan efisien :-)
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]

The extend()metode dalam contoh Anda memodifikasi xbukannya kembali nilai yang berguna (yangreduce() mengharapkan).

Cara yang lebih cepat untuk melakukan reduceversi itu

>>> 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)akan menjadi cara yang benar untuk melakukan reduceversi. Built-in lebih cepat dari lambdas.
agf

3
@agf di sini adalah caranya: * 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
Ini adalah algoritma Shlemiel sang pelukis joelonsoftware.com/articles/fog0000000319.html
Mike Graham

2
ini hanya dapat digunakan untuk integers. Tetapi bagaimana jika daftar berisi string?
Freddy

3
@ Freddy: operator.addFungsi ini berfungsi dengan baik untuk daftar bilangan bulat dan daftar string.
Greg Hewgill

121

Jangan menemukan kembali roda jika Anda menggunakan Django :

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

... Pandaas :

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

... Itertools :

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

... Matplotlib

>>> 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_iterableharus menjadi jawaban yang tepat
tokek

3
jawaban bagus! bekerja juga untuk l = [[[1, 2, 3], [4, 5]], 5] dalam kasus panda
Markus Dutschke

1
Saya suka solusi Pandas. Jika Anda memiliki sesuatu seperti: list_of_menuitems = [1, 2, [3, [4, 5, [6]]]], maka akan mengakibatkan pada: [1, 2, 3, 4, 5, 6]. Yang saya rindukan adalah level rata.
imjoseangel

115

Berikut ini adalah pendekatan umum yang berlaku untuk angka , string , daftar bersarang dan campuran wadah .

Kode

#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

Catatan :

  • Dalam Python 3, yield from flatten(x) bisa digantifor sub_x in flatten(x): yield sub_x
  • Dalam Python 3.8, kelas dasar abstrak yang pindah dari collection.abcke typingmodul.

Demo

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

Referensi

  • Solusi ini dimodifikasi dari resep di Beazley, D. dan B. Jones. Resep 4.14, Python Cookbook 3rd Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Menemukan pos SO sebelumnya , mungkin demonstrasi asli.

5
Saya baru saja menulis hampir sama, karena saya tidak melihat solusi Anda ... di sini adalah apa yang saya cari "secara rekursif meratakan beberapa daftar lengkap" ... (+1)
Martin Thoma

3
@MartinThoma Sangat dihargai. FYI, jika meratakan iterables bersarang adalah praktik umum bagi Anda, ada beberapa paket pihak ketiga yang menangani ini dengan baik. Ini dapat menghemat dari menciptakan kembali roda. Saya telah menyebutkan more_itertoolsantara lain yang dibahas dalam posting ini. Bersulang.
pylang

Mungkin traversejuga bisa menjadi nama yang bagus untuk sebatang pohon ini, sedangkan aku akan membuatnya kurang universal untuk jawaban ini dengan menempel pada daftar bersarang.
Wolf

Anda dapat memeriksa if hasattr(x, '__iter__')alih-alih mengimpor / memeriksa Iterabledan itu akan mengecualikan string juga.
Ryan Allen

kode di atas tampaknya tidak berfungsi jika salah satu dari daftar bersarang memiliki daftar string. [1, 2, [3, 4], [4], [], 9, 9.5, 'ssssss', ['str', 'sss', 'ss'], [3, 4, 5]] output: - [1, 2, 3, 4, 4, 9, 9.5, 'ssssss', 3, 4, 5]
sunnyX

51

Jika Anda ingin meratakan struktur data di mana Anda tidak tahu seberapa dalam sarangnya Anda bisa menggunakan 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]

Ini generator sehingga Anda perlu memberikan hasilnya ke listatau secara eksplisit beralih di atasnya.


Untuk meratakan hanya satu tingkat dan jika masing-masing item itu sendiri iterable Anda juga dapat menggunakan iteration_utilities.flattenyang itu sendiri hanya pembungkus tipis di sekitar 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]

Hanya untuk menambahkan beberapa timing (berdasarkan jawaban Nico Schlömer yang tidak menyertakan fungsi yang disajikan dalam jawaban ini):

masukkan deskripsi gambar di sini

Ini adalah plot log-log untuk mengakomodasi rentang nilai yang sangat besar. Untuk alasan kualitatif: Lebih rendah lebih baik.

Hasil penelitian menunjukkan bahwa jika iterable hanya berisi beberapa iterables internal maka sumakan lebih cepat, namun untuk iterables lama hanya itertools.chain.from_iterable, iteration_utilities.deepflattenatau pemahaman bersarang memiliki kinerja yang masuk akal dengan itertools.chain.from_iterablemenjadi yang tercepat (seperti yang telah diperhatikan oleh Nico Schlömer).

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

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

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

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

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

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

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

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

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


from simple_benchmark import benchmark

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

b.plot()

1 Penafian: Saya penulis perpustakaan itu


sumtidak lagi bekerja pada urutan sewenang-wenang seperti yang dimulai dengan 0, membuat functools.reduce(operator.add, sequences)penggantian (bukankah kita senang mereka dihapus reducedari builtin?). Ketika jenis diketahui mungkin lebih cepat untuk digunakan type.__add__.
Yann Vernier

@YannVernier Terima kasih atas informasinya. Saya pikir saya menjalankan tolok ukur ini pada Python 3.6 dan berhasil sum. Apakah Anda mengetahui versi Python mana yang berhenti berfungsi?
MSeifert

Saya agak keliru. 0hanyalah nilai awal default, jadi ini berfungsi jika seseorang menggunakan argumen awal untuk memulai dengan daftar kosong ... tapi itu masih berupa case strings khusus dan memberitahu saya untuk menggunakan join. Ini menerapkan foldlbukan foldl1. Masalah yang sama muncul di 2.7.
Yann Vernier

39

Saya mengambil kembali pernyataan saya. jumlah bukanlah pemenang. Meskipun lebih cepat ketika daftar kecil. Tetapi kinerjanya menurun secara signifikan dengan daftar yang lebih besar.

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

Versi penjumlahannya masih berjalan lebih dari satu menit dan belum selesai diproses!

Untuk daftar sedang:

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

Menggunakan daftar kecil dan batas waktu: angka = 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
untuk daftar yang benar-benar sangat kecil, misalnya satu dengan 3 sublists, mungkin - tetapi karena kinerja penjumlahan berjalan dengan O (N ** 2) sementara pemahaman daftar berjalan dengan O (N), hanya menumbuhkan daftar input sedikit akan membalikkan hal-hal - - memang LC akan "jauh lebih cepat" dari jumlah pada batas ketika N tumbuh. Saya bertanggung jawab untuk merancang penjumlahan dan melakukan implementasi pertamanya dalam runtime Python, dan saya masih berharap saya telah menemukan cara untuk secara efektif membatasi penjumlahan ke penjumlahan angka (apa yang benar-benar bagus) dan memblokir "gangguan menarik" yang ditawarkan kepada orang-orang yang ingin "menjumlahkan" daftar ;-).
Alex Martelli

38

Tampaknya ada kebingungan dengan operator.add! Saat Anda menambahkan dua daftar bersama, istilah yang benar untuk itu adalah concat, bukan menambahkan.operator.concatadalah apa yang perlu Anda gunakan.

Jika Anda berpikir fungsional, semudah ini ::

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

Anda melihat mengurangi menghormati jenis urutan, jadi ketika Anda memasok tuple, Anda mendapatkan kembali tuple. Mari kita coba dengan daftar ::

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

Aha, Anda mendapatkan kembali daftar.

Bagaimana dengan kinerja ::

>>> 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_iterablecukup cepat! Tetapi tidak ada perbandingan untuk dikurangi dengan 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
Hmm agar adil, contoh kedua harus daftar juga (atau tupel pertama?)
Mr_and_Mrs_D

2
Menggunakan input kecil seperti itu tidak banyak perbandingan yang adil. Untuk 1000 urutan panjang 1000, saya mendapatkan 0,037 detik untuk list(chain.from_iterable(...))dan 2,5 detik untuk reduce(concat, ...). Masalahnya adalah yang reduce(concat, ...)memiliki runtime kuadrat, sedangkan chainlinear.
kaya3

33

Mengapa Anda menggunakan ext?

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

Ini seharusnya bekerja dengan baik.


7
untuk python3from functools import reduce
andorov

Maaf itu sangat lambat lihat sisa jawaban
Mr_and_Mrs_D

Sejauh ini, ini adalah solusi termudah untuk memahami namun singkat yang bekerja pada Python 2 dan 3. Saya menyadari bahwa banyak orang Python dalam pemrosesan data di mana ada sejumlah besar data untuk diproses dan dengan demikian sangat peduli tentang kecepatan, tetapi ketika Anda menulis skrip shell dan hanya memiliki beberapa lusin elemen dalam beberapa sub-daftar, maka ini sempurna.
Asfand Qazi

27

Pertimbangkan untuk menginstal more_itertoolspaket.

> pip install more_itertools

Ini dikirimkan dengan implementasi untuk flatten( sumber , dari resep 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]

Pada versi 2.4, Anda dapat meratakan iterables yang lebih rumit dan bersarang more_itertools.collapse( sumber , disumbangkan oleh 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]

Memang. Ini harus menjadi jawaban yang diterima
brunetton

Jika Anda mampu menambahkan paket ke proyek Anda - jawaban ini adalah yang terbaik
viddik13

22

Alasan fungsi Anda tidak berfungsi adalah karena ekstensi memanjang array di tempat dan tidak mengembalikannya. Anda masih dapat mengembalikan x dari lambda, menggunakan sesuatu seperti ini:

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

Catatan: ekstensi lebih efisien daripada + pada daftar.


7
extendlebih baik digunakan sebagai newlist = [], extend = newlist.extend, for sublist in l: extend(l)karena menghindari (agak besar) overhead dari lambda, atribut lookup pada x, dan or.
agf

untuk python 3 addfrom functools import reduce
Markus Dutschke

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

Versi rekursif

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
bagus, tidak perlu impor dan jelas apa yang dilakukannya ... meratakan daftar, titik :)
Goran B.

1
cukup brilian!
Sachin Sharma

15

matplotlib.cbook.flatten() akan bekerja untuk daftar bersarang bahkan jika mereka bersarang lebih dalam dari contoh.

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

Hasil:

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

Ini 18x lebih cepat dari garis bawah ._. Ratakan:

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

Jawaban yang diterima tidak berfungsi untuk saya ketika berhadapan dengan daftar panjang variabel berdasarkan teks. Berikut adalah pendekatan alternatif yang berhasil bagi saya.

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

Jawaban yang diterima yang tidak berhasil:

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

Baru yang diusulkan solusi yang melakukan pekerjaan untuk saya:

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

Fitur buruk dari fungsi Anil di atas adalah mengharuskan pengguna untuk selalu secara manual menentukan argumen kedua sebagai daftar kosong [] . Ini seharusnya menjadi default. Karena cara kerja objek Python, ini harus diatur di dalam fungsi, bukan dalam argumen.

Ini fungsi yang berfungsi:

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

Pengujian:

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

Mengikuti tampaknya paling sederhana bagi saya:

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

Tidak berfungsi untuk daftar dengan dimensi berbeda. -1
nurub

10

Satu juga dapat menggunakan flat NumPy :

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

Sunting 11/02/2016: Hanya berfungsi ketika sublists memiliki dimensi yang identik.


apakah itu solusi optimal?
RetroCode

6

Anda bisa menggunakan numpy:
flat_list = list(np.concatenate(list_of_list))


Ini berfungsi untuk numerik, string, dan daftar campuran juga
Nitin

2
Gagal untuk data bersarang yang tidak rata, seperti[1, 2, [3], [[4]], [5, [6]]]
EL_DON

5

Jika Anda bersedia memberikan sedikit kecepatan untuk tampilan yang lebih bersih, maka Anda dapat menggunakan numpy.concatenate().tolist()atau 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

Anda dapat menemukan lebih banyak di sini di docs numpy.concatenate dan numpy.ravel


1
Tidak berfungsi untuk daftar bersarang yang tidak rata seperti[1, 2, [3], [[4]], [5, [6]]]
EL_DON

5

Solusi tercepat yang saya temukan (untuk daftar besar):

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

Selesai! Tentu saja Anda dapat mengubahnya kembali menjadi daftar dengan menjalankan daftar (l)


1
Ini salah, perataan akan mengurangi dimensi array nd menjadi satu, tetapi tidak menyatukan daftar di dalamnya sebagai satu.
Ando Jurai

5

Kode sederhana untuk underscore.pykipas paket

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

Ini memecahkan semua masalah mendatar (tidak ada item daftar atau bersarang kompleks)

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]

Anda dapat menginstal underscore.pydengan pip

pip install underscore.py

Demikian pula, Anda dapat menggunakan pydash . Saya menemukan versi ini jauh lebih mudah dibaca daripada pemahaman daftar atau jawaban lainnya.
gliemezis

2
Ini sangat lambat.
Nico Schlömer

2
Mengapa ada modul bernama _? Itu sepertinya nama yang buruk. Lihat stackoverflow.com/a/5893946/6605826
EL_DON

2
@EL_DON: Dari halaman readme underscore.py "Underscore.py adalah port python dari pustaka javascript underscore.js yang sangat baik". Saya pikir itu alasan untuk nama ini. Dan ya, itu bukan nama yang baik untuk python
Vu Anh

5
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])

Gagal untuk python2.7 untuk daftar contoh bersarang dalam pertanyaan:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON

@EL_DON diuji pada python 2.7.5. itu berfungsi dengan baik
englealuze

5

Catatan : Di bawah ini berlaku untuk Python 3.3+ karena digunakan yield_from. sixjuga merupakan paket pihak ketiga, meskipun stabil. Bergantian, Anda bisa menggunakan sys.version.


Dalam hal obj = [[1, 2,], [3, 4], [5, 6]], semua solusi di sini baik, termasuk pemahaman daftar dan itertools.chain.from_iterable.

Namun, pertimbangkan kasus yang sedikit lebih rumit ini:

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

Ada beberapa masalah di sini:

  • Satu elemen,, 6hanyalah skalar; itu tidak dapat diubah, sehingga rute di atas akan gagal di sini.
  • Salah satu elemen, 'abc', adalah teknis iterable (semuastr s adalah). Namun, membaca yang tersirat sedikit, Anda tidak ingin memperlakukannya seperti itu - Anda ingin memperlakukannya sebagai elemen tunggal.
  • Elemen terakhir, [8, [9, 10]]itu sendiri adalah iterable bersarang. Pemahaman daftar dasar dan chain.from_iterablehanya mengekstrak "1 level ke bawah."

Anda dapat memperbaiki ini sebagai berikut:

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

Di sini, Anda memeriksa bahwa sub-elemen (1) dapat diubah dengan Iterable, sebuah ABC dari itertools, tetapi juga ingin memastikan bahwa (2) elemen tersebut tidak seperti "string-like."


1
Jika Anda masih tertarik dengan kompatibilitas Python 2, ubah yield fromke forloop, misalnyafor x in flatten(i): yield x
pylang

5
flat_list = []
for i in list_of_list:
    flat_list+=i

Kode ini juga berfungsi dengan baik karena hanya memperpanjang daftar sepanjang jalan. Meskipun sangat mirip tetapi hanya memiliki satu untuk loop. Jadi itu memiliki kompleksitas lebih sedikit daripada menambahkan 2 untuk loop.


5
from nltk import flatten

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

Keuntungan dari solusi ini daripada kebanyakan orang lain di sini adalah jika Anda memiliki daftar seperti:

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

sementara sebagian besar solusi lain membuat kesalahan, solusi ini menangani mereka.


Pertanyaannya menyatakan "daftar daftar", tetapi daftar contoh Anda menyertakan item yang tidak tercantum. Sebagian besar solusi lain menempel pada pertanyaan awal. Solusi Anda memecahkan masalah yang lebih luas, tetapi juga membutuhkan paket Python non-basis (nltk) yang harus diinstal terlebih dahulu.
simonobo

4

Ini mungkin bukan cara yang paling efisien tetapi saya berpikir untuk menempatkan satu-liner (sebenarnya dua-liner). Kedua versi akan bekerja pada daftar bersarang hierarki sewenang-wenang, dan mengeksploitasi fitur bahasa (Python3.5) dan rekursi.

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)

Outputnya adalah

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

Ini bekerja secara mendalam terlebih dahulu. Rekursi turun hingga menemukan elemen non-daftar, lalu memperluas variabel lokal flistdan kemudian mengembalikannya ke induk. Setiap kali flistdikembalikan, diperpanjang ke orang tua flistdalam pemahaman daftar. Oleh karena itu, pada root, daftar datar dikembalikan.

Yang di atas membuat beberapa daftar lokal dan mengembalikannya yang digunakan untuk memperpanjang daftar induk. Saya pikir jalan keluar untuk ini mungkin menciptakan gloabl flist, seperti di bawah ini.

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)

Outputnya lagi

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

Meskipun saya tidak yakin saat ini tentang efisiensi.


Mengapa perluas ([l]) alih-alih tambahkan (l)?
Maciek

3

Pendekatan lain yang tidak biasa yang berfungsi untuk daftar bilangan bulat hetero dan homogen:

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

Itu hanya cara yang lebih rumit dan sedikit lebih lambat dari apa yang posted3000 sudah diposting sebelumnya. Saya menemukan kembali usulannya kemarin, jadi pendekatan ini tampaknya cukup populer akhir-akhir ini;)
Darkonaut

Tidak cukup: 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

kode saya sebagai satu liner adalah: flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
tharndt

1
Anda memang benar +1, proposal 0003000 tidak akan berfungsi dengan beberapa digit angka, saya juga tidak menguji ini sebelumnya meskipun harus jelas. Anda dapat menyederhanakan kode Anda dan menulis [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. Tapi saya sarankan untuk tetap dengan proposal Deleet untuk kasus penggunaan nyata. Itu tidak mengandung transformasi tipe hacky, lebih cepat dan lebih fleksibel karena secara alami juga menangani daftar dengan tipe campuran.
Darkonaut

2
Sayangnya tidak ada. Tetapi saya melihat kode ini baru-baru ini di sini: Python Practice Book 6.1.2
tharndt
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.