Pertanyaan Membuat daftar datar dari daftar daftar dengan Python


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

Saya bisa melakukan itu untuk loop, tapi mungkin ada beberapa "one-liner" yang keren? Saya mencobanya mengurangi, tapi saya mendapat 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'

2069
2018-06-04 20:30


asal


Jawaban:


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

yang berarti:

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

lebih cepat daripada pintasan yang diposkan sejauh ini. (l adalah daftar untuk meratakan.)

Berikut ini adalah fungsi terkait:

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

Untuk bukti, seperti biasa, Anda dapat menggunakan timeit modul di pustaka 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: pintas berdasarkan + (termasuk penggunaan yang tersirat dalam sum) adalah, kebutuhan, O(L**2) ketika ada L sublists - sebagai daftar hasil antara terus semakin panjang, pada setiap langkah objek daftar hasil antara baru akan dialokasikan, dan semua item dalam hasil tengah sebelumnya harus disalin (serta beberapa yang baru ditambahkan pada akhirnya). Jadi (untuk kesederhanaan dan tanpa kehilangan umum yang sebenarnya), katakanlah Anda memiliki L subliter dari setiap item: butir pertama I disalin bolak-balik L-1 kali, item I kedua L-2 kali, dan seterusnya; jumlah total salinan adalah saya kali jumlah x untuk x dari 1 hingga L dikecualikan, yaitu, I * (L**2)/2.

List comprehension hanya menghasilkan satu daftar, satu kali, dan menyalin setiap item di atas (dari tempat asal tempat tinggal ke daftar hasil) juga tepat satu kali.


2984
2018-06-04 20:37



Kamu dapat memakai itertools.chain():

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

atau, pada Python> = 2.6, gunakan itertools.chain.from_iterable() yang tidak perlu membongkar daftar:

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

Pendekatan ini bisa dibilang lebih mudah dibaca daripada [item for sublist in l for item in sublist] dan tampaknya lebih cepat juga:

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

1079
2018-06-04 21:06



Catatan dari penulis: Ini tidak efisien. Tapi menyenangkan, karena monads luar biasa. Ini tidak sesuai untuk produksi kode Python.

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

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

Karena Anda menjumlahkan daftar bertingkat, Anda benar-benar mendapatkan [1,3]+[2,4] sebagai hasil 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 perlu solusi lain.


636
2018-06-04 20:35



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

list(itertools.chain.from_iterable(a))

menjadi solusi tercepat (jika lebih dari 10 daftar digabung).

enter image description here


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 itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


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


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

129
2017-07-26 09:38



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]

Itu extend() metode dalam contoh Anda memodifikasi x bukannya mengembalikan nilai yang berguna (yang reduce() mengharapkan).

Cara yang lebih cepat untuk melakukan itu reduce versi akan

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

99
2018-06-04 20:35



Berikut ini adalah pendekatan umum yang berlaku untuk angka, string, bersarang daftar dan campur aduk kontainer.

Kode

from collections import Iterable


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

Catatan: dengan Python 3, yield from flatten(x) dapat menggantikan for sub_x in flatten(x): yield sub_x

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 edisi ke-3., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Ditemukan sebelumnya Posting SO, mungkin demonstrasi asli.

54
2017-11-29 04:14



Saya mengambil kembali pernyataan saya. Jumlah bukanlah pemenang. Meskipun lebih cepat ketika daftar kecil. Tetapi kinerja 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 penjumlahan 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 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

31
2018-06-04 20:46



Mengapa Anda menggunakan perpanjangan?

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

Ini harus berfungsi dengan baik.


25
2018-06-04 20:38



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

Jika Anda berpikir fungsional, semudah seperti ini ::

>>> 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 jenis urutan, jadi ketika Anda menyediakan tuple, Anda mendapatkan kembali tuple. mari 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 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_itable cukup cepat! Tetapi tidak ada perbandingan untuk mengurangi 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

19
2017-09-14 15:09