Làm thế nào để tạo một danh sách phẳng ra khỏi danh sách danh sách?


3373

Tôi tự hỏi liệu có một phím tắt để tạo một danh sách đơn giản ra khỏi danh sách các danh sách trong Python.

Tôi có thể làm điều đó trong một forvòng lặp, nhưng có lẽ có một "lớp lót" thú vị? Tôi đã thử nó với reduce(), nhưng tôi nhận được một lỗi.

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

Thông báo lỗi

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
Có một cuộc thảo luận chuyên sâu về vấn đề này ở đây: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , thảo luận về một số phương pháp làm phẳng danh sách các danh sách lồng nhau tùy ý. Một đọc thú vị!
RichieHulum

6
Một số câu trả lời khác tốt hơn nhưng lý do khiến bạn thất bại là phương pháp 'mở rộng' luôn trả về Không. Đối với một danh sách có độ dài 2, nó sẽ hoạt động nhưng không trả về. Đối với một danh sách dài hơn, nó sẽ tiêu thụ 2 đối số đầu tiên, trả về Không có. Sau đó, nó tiếp tục với none.extend (<third arg>), nguyên nhân gây ra lỗi này
mehtunguh

Giải pháp @ shawn-chin là pythonic nhiều hơn ở đây, nhưng nếu bạn cần bảo tồn loại trình tự, giả sử bạn có một bộ dữ liệu thay vì danh sách các danh sách, thì bạn nên sử dụng giảm (toán tử.concat, tuple_of_tuples). Sử dụng toán tử.concat với các bộ dữ liệu dường như hoạt động nhanh hơn chain.from_iterables với danh sách.
Meitham

Câu trả lời:


4794

Đưa ra một danh sách các danh sách l,

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

nghĩa là:

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

nhanh hơn các phím tắt được đăng cho đến nay. ( llà danh sách để làm phẳng.)

Đây là chức năng tương ứng:

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

Để làm bằng chứng, bạn có thể sử dụng timeitmô-đun trong thư viện chuẩn:

$ 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

Giải thích: các phím tắt dựa trên +(bao gồm cả việc sử dụng ngụ ý sum) là cần thiết, O(L**2)khi có danh sách con L - vì danh sách kết quả trung gian tiếp tục dài hơn, ở mỗi bước, một đối tượng danh sách kết quả trung gian mới được phân bổ và tất cả các mục trong kết quả trung gian trước đó phải được sao chép lại (cũng như một vài kết quả mới được thêm vào cuối). Vì vậy, để đơn giản và không mất tính tổng quát thực tế, giả sử bạn có danh sách con L của các mục I mỗi mục: các mục I đầu tiên được sao chép qua lại L-1 lần, lần thứ hai tôi ghi L-2 lần, v.v. tổng số bản sao là I lần tổng của x cho x từ 1 đến L bị loại trừ, nghĩa là , I * (L**2)/2.

Việc hiểu danh sách chỉ tạo một danh sách, một lần và sao chép từng mục qua (từ nơi cư trú ban đầu của nó sang danh sách kết quả) cũng chính xác một lần.


486
Tôi đã thử một bài kiểm tra với cùng một dữ liệu, sử dụng 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))'. Nó chạy nhanh hơn gấp đôi so với mức độ hiểu danh sách lồng nhau nhanh nhất trong số các lựa chọn thay thế được hiển thị ở đây.
trực giác

274
Tôi tìm thấy cú pháp khó hiểu cho đến khi tôi nhận ra bạn có thể nghĩ về nó chính xác như được lồng cho các vòng lặp. cho danh sách phụ trong l: cho mục trong danh sách phụ: mục sản lượng
Rob Crowell

23
@BorisChervenkov: Lưu ý rằng tôi đã kết thúc cuộc gọi list()để nhận ra trình lặp vào danh sách.
trực giác

163
[lá cho cây trong rừng cho lá trong cây] có thể dễ hiểu và áp dụng hơn.
John Mee

80
@Joel, thực sự ngày nay list(itertools.chain.from_iterable(l))là tốt nhất - như được chú ý trong các bình luận khác và câu trả lời của Shawn.
Alex Martelli

1568

Bạn có thể sử dụng itertools.chain():

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

Hoặc bạn có thể sử dụng itertools.chain.from_iterable()mà không yêu cầu giải nén danh sách với *toán tử :

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

13
Đây *là điều khó khăn làm cho chainít đơn giản hơn so với việc hiểu danh sách. Bạn phải biết rằng chuỗi chỉ kết hợp với nhau các lần lặp được truyền dưới dạng tham số và * làm cho danh sách cấp cao nhất được mở rộng thành tham số, do đó chainkết hợp tất cả các lần lặp đó lại với nhau, nhưng không giảm thêm nữa. Tôi nghĩ rằng điều này làm cho sự hiểu biết dễ đọc hơn so với việc sử dụng chuỗi trong trường hợp này.
Tim Dierks

52
@TimDierks: Tôi không chắc chắn "điều này đòi hỏi bạn phải hiểu cú pháp Python" là một đối số chống lại việc sử dụng một kỹ thuật nhất định trong Python. Chắc chắn, việc sử dụng phức tạp có thể gây nhầm lẫn, nhưng toán tử "splat" thường hữu ích trong nhiều trường hợp và điều này không sử dụng nó theo cách đặc biệt khó hiểu; từ chối tất cả các tính năng ngôn ngữ không nhất thiết phải rõ ràng đối với người dùng bắt đầu có nghĩa là bạn đang buộc một tay sau lưng. Cũng có thể loại bỏ sự hiểu biết danh sách trong khi bạn đang ở đó; Người dùng từ các nền tảng khác sẽ tìm thấy một forvòng lặp lặp đi lặp lại appendrõ ràng hơn.
ShadowRanger

Câu trả lời này và các câu trả lời khác ở đây, đưa ra kết quả không chính xác nếu cấp cao nhất cũng chứa một giá trị. chẳng hạn, list = [["abc","bcd"],["cde","def"],"efg"]sẽ dẫn đến kết quả là["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr

Có vẻ như *toán tử không thể được sử dụng trong python2
wkm

907

Lưu ý từ tác giả : Điều này là không hiệu quả. Nhưng vui, bởi vì đơn sắc là tuyệt vời. Nó không thích hợp để sản xuất mã Python.

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

Điều này chỉ tính tổng các phần tử của iterable được truyền trong đối số thứ nhất, coi đối số thứ hai là giá trị ban đầu của tổng (nếu không được đưa ra, 0 được sử dụng thay thế và trường hợp này sẽ gây ra lỗi cho bạn).

Bởi vì bạn đang tổng hợp các danh sách lồng nhau, bạn thực sự nhận được [1,3]+[2,4]kết quả là sum([[1,3],[2,4]],[]), tương đương với[1,3,2,4] .

Lưu ý rằng chỉ hoạt động trên danh sách danh sách. Đối với danh sách danh sách các danh sách, bạn sẽ cần một giải pháp khác.


100
Điều đó khá gọn gàng và thông minh nhưng tôi sẽ không sử dụng nó vì nó khó đọc.
andrewrk

87
Đây là một thuật toán của họa sĩ Shlemiel joelonsoftware.com/articles/fog0000000319.html - không hiệu quả một cách không cần thiết cũng như xấu xí không cần thiết.
Mike Graham

44
Hoạt động chắp thêm trên các danh sách tạo thành một Monoid, đây là một trong những khái niệm trừu tượng thuận tiện nhất để nghĩ về một +hoạt động theo nghĩa chung (không giới hạn chỉ số). Vì vậy, câu trả lời này xứng đáng được +1 từ tôi để xử lý (chính xác) danh sách dưới dạng đơn hình. Buổi biểu diễn có liên quan mặc dù ...
ulidtko

7
@andrewrk Chà, một số người nghĩ rằng đây là cách làm sạch nhất: youtube.com/watch?v=IOiZatlZtGU những người không hiểu tại sao điều này tuyệt vời chỉ cần đợi vài thập kỷ cho đến khi mọi người làm theo cách này: ) hãy sử dụng các ngôn ngữ lập trình (và trừu tượng) được phát hiện và không được phát minh, Monoid được phát hiện.
jhegedus

11
đây là một cách rất không hiệu quả vì khía cạnh bậc hai của tổng.
Jean-François Fabre

460

Tôi đã thử nghiệm hầu hết các giải pháp được đề xuất với perfplot (một dự án thú cưng của tôi, về cơ bản là bao bọc xung quanh timeit), và tìm thấy

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

là giải pháp nhanh nhất, cả khi nhiều danh sách nhỏ và một vài danh sách dài được nối với nhau. ( operator.iaddcũng nhanh không kém.)

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây


Mã để tái tạo cốt truyện:

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
Đối với các danh sách lồng nhau rất lớn, 'list (numpy.array (a) .flat)' là nhanh nhất trong số tất cả các chức năng trên.
Sara

Đã thử sử dụng regex: 'list (map (int, re.findall (r "[\ w] +", str (a))))'. Tốc độ chậm hơn một chút mà numpy_concatenate
Justas

Có cách nào để làm một perfplot 3-d? số lượng mảng bằng kích thước trung bình của mảng?
Leo

Tôi yêu giải pháp của bạn. Ngắn gọn, đơn giản và hiệu quả :-)
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]

Các extend()phương pháp trong ví dụ của bạn sẽ thay đổi xthay vì trả lại một giá trị hữu ích (màreduce() hy vọng).

Một cách nhanh hơn để làm reducephiên bản sẽ là

>>> 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)sẽ là cách chính xác để làm reducephiên bản. Được xây dựng nhanh hơn lambdas.
agf

3
@agf đây là cách làm: * 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
Đây là thuật toán của họa sĩ Shlemiel joelonsoftware.com/articles/fog0000000319.html
Mike Graham

2
Điều này chỉ có thể sử dụng cho integers. Nhưng nếu danh sách chứa string?
Freddy

3
@Freddy: operator.addHàm hoạt động tốt như nhau cho cả danh sách số nguyên và danh sách chuỗi.
Greg Hewgill

120

Đừng phát minh lại bánh xe nếu bạn sử dụng Django :

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

... Gấu trúc :

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

... Đơn phương :

>>> from unipath.path import flatten
>>> list(flatten(l))

... setuptools :

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

4
flatten = itertools.chain.from_iterablenên là câu trả lời đúng
tắc kè

3
câu trả lời chính xác! cũng hoạt động với l = [[[1, 2, 3], [4, 5]], 5] trong trường hợp gấu trúc
Markus Dutschke

1
Tôi thích giải pháp Pandas. Nếu bạn có một cái gì đó như : list_of_menuitems = [1, 2, [3, [4, 5, [6]]]], nó sẽ dẫn đến : [1, 2, 3, 4, 5, 6]. Những gì tôi bỏ lỡ là mức độ phẳng.
imjoseangel

115

Đây là một cách tiếp cận chung áp dụng cho số , chuỗi , danh sách lồng nhauhỗn hợp thùng chứa .

#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

Ghi chú :

  • Trong Python 3, yield from flatten(x) có thể thay thếfor sub_x in flatten(x): yield sub_x
  • Trong Python 3.8, lớp cơ sở trừu tượng được chuyển từ collection.abcvào typingmô-đun.

Bản giới thiệu

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

Tài liệu tham khảo

  • Giải pháp này được sửa đổi từ một công thức trong Beazley, D. và B. Jones. Công thức 4.14, Python Cookbook 2nd Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Tìm thấy một bài viết SO trước đó , có thể là các cuộc biểu tình ban đầu.

5
Tôi chỉ viết khá giống nhau, vì tôi không thấy giải pháp của bạn ... đây là những gì tôi tìm kiếm "đệ quy hoàn thành nhiều danh sách" ... (+1)
Martin Thoma

3
@MartinThoma Rất nhiều đánh giá cao. FYI, nếu làm phẳng các vòng lặp lồng nhau là một cách phổ biến đối với bạn, có một số gói của bên thứ ba xử lý tốt việc này. Điều này có thể tiết kiệm từ việc phát minh lại bánh xe. Tôi đã đề cập more_itertoolstrong số những người khác được thảo luận trong bài viết này. Chúc mừng.
pylang

Có lẽ traversecũng có thể là một cái tên hay cho cách trồng cây này, trong khi tôi sẽ giữ nó ít phổ biến hơn cho câu trả lời này bằng cách bám vào các danh sách lồng nhau.
Sói

Bạn có thể kiểm tra if hasattr(x, '__iter__')thay vì nhập / kiểm tra Iterablevà điều đó cũng sẽ loại trừ các chuỗi.
Ryan Allen

đoạn mã trên dường như không hoạt động nếu một trong các danh sách lồng nhau có danh sách các chuỗi. [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

Nếu bạn muốn làm phẳng cấu trúc dữ liệu mà bạn không biết nó được lồng sâu đến mức nào, bạn có thể sử dụng 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]

Đó là một trình tạo, do đó bạn cần đưa kết quả thành một listhoặc lặp lại rõ ràng trên nó.


Để làm phẳng chỉ một cấp độ và nếu mỗi mục tự lặp lại, bạn cũng có thể sử dụng iteration_utilities.flattenchính nó chỉ là một lớp bọc mỏng xung quanh 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]

Chỉ cần thêm một số thời gian (dựa trên câu trả lời của Nico Schlömer không bao gồm chức năng được trình bày trong câu trả lời này):

nhập mô tả hình ảnh ở đây

Đó là một biểu đồ log-log để phù hợp với phạm vi giá trị khổng lồ được kéo dài. Đối với lý luận định tính: Thấp hơn là tốt hơn.

Kết quả cho thấy rằng nếu iterable chỉ chứa một vài lần lặp bên trong thì sumsẽ nhanh nhất, tuy nhiên đối với các lần lặp dài chỉ itertools.chain.from_iterable, iteration_utilities.deepflattenhoặc sự hiểu biết lồng nhau có hiệu suất hợp lý với itertools.chain.from_iterabletốc độ nhanh nhất (như đã được chú ý bởi 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 Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của thư viện đó


sumkhông còn hoạt động trên các chuỗi tùy ý khi nó bắt đầu 0, tạo ra functools.reduce(operator.add, sequences)sự thay thế (không phải chúng tôi vui mừng khi chúng bị xóa reducekhỏi nội dung sao?). Khi các loại được biết nó có thể được sử dụng nhanh hơn type.__add__.
Yann Vernier

@YannVernier Cảm ơn thông tin. Tôi nghĩ rằng tôi đã chạy các điểm chuẩn này trên Python 3.6 và nó đã hoạt động với sum. Bạn có biết phiên bản Python nào nó ngừng hoạt động không?
MSeifert

Tôi đã hơi nhầm. 0chỉ là giá trị bắt đầu mặc định, vì vậy nó hoạt động nếu một người sử dụng đối số bắt đầu để bắt đầu với một danh sách trống ... nhưng nó vẫn là chuỗi trường hợp đặc biệt và bảo tôi sử dụng phép nối. Nó đang thực hiện foldlthay vì foldl1. Vấn đề tương tự xuất hiện trong 2.7.
Yann Vernier

39

Tôi lấy lại tuyên bố của mình. tổng không phải là người chiến thắng. Mặc dù nó nhanh hơn khi danh sách nhỏ. Nhưng hiệu suất suy giảm đáng kể với danh sách lớn hơn.

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

Phiên bản sum vẫn đang chạy trong hơn một phút và chưa xử lý xong!

Đối với danh sách trung bình:

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

Sử dụng danh sách nhỏ và thời gian: 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
đối với một danh sách rất nhỏ, ví dụ: một danh sách có 3 danh sách con, có thể - nhưng vì hiệu suất của sum đi với O (N ** 2) trong khi việc hiểu danh sách đi với O (N), chỉ cần tăng danh sách đầu vào một chút sẽ đảo ngược mọi thứ - - thực sự LC sẽ "nhanh hơn vô hạn" so với tổng ở giới hạn khi N tăng trưởng. Tôi chịu trách nhiệm thiết kế tổng và thực hiện lần đầu tiên trong thời gian chạy Python và tôi vẫn ước mình đã tìm ra cách hạn chế nó một cách hiệu quả để tổng hợp số (điều thực sự tốt) và chặn "phiền toái hấp dẫn" mà nó mang lại cho mọi người ai muốn "tổng hợp" danh sách ;-).
Alex Martelli

38

Dường như có một sự nhầm lẫn với operator.add! Khi bạn thêm hai danh sách với nhau, thuật ngữ chính xác cho điều đó là concat, không thêm. operator.concatlà những gì bạn cần sử dụng.

Nếu bạn đang suy nghĩ chức năng, nó dễ như thế này ::

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

Bạn thấy giảm tôn trọng loại trình tự, vì vậy khi bạn cung cấp một tuple, bạn sẽ nhận lại một tuple. Hãy thử với một danh sách ::

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

Aha, bạn nhận lại một danh sách.

Làm thế nào về hiệu suất ::

>>> 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_iterablelà khá nhanh! Nhưng nó không so sánh để giảm với 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 là ví dụ thứ hai công bằng nên được liệt kê (hoặc tuple đầu tiên?)
Mr_and_Mrs_D

2
Sử dụng đầu vào nhỏ như vậy không phải là một so sánh công bằng. Đối với 1000 chuỗi có độ dài 1000, tôi nhận được 0,037 giây list(chain.from_iterable(...))và 2,5 giây cho reduce(concat, ...). Vấn đề là reduce(concat, ...)có thời gian chạy bậc hai, trong khi đó chainlà tuyến tính.
kaya3

33

Tại sao bạn sử dụng mở rộng?

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

Điều này sẽ làm việc tốt.


7
cho python3from functools import reduce
andorov

Xin lỗi, điều đó rất chậm, xem phần còn lại của câu trả lời
Mr_and_Mrs_D

Đây là giải pháp ngắn gọn nhưng dễ hiểu nhất hoạt động trên Python 2 và 3. Tôi nhận ra rằng có rất nhiều người Python đang xử lý dữ liệu trong đó có lượng dữ liệu khổng lồ để xử lý và do đó quan tâm rất nhiều đến tốc độ, nhưng khi bạn đang viết một kịch bản shell và chỉ có vài chục thành phần trong một vài danh sách phụ, thì điều này là hoàn hảo.
Asfand Qazi

27

Xem xét việc cài đặt more_itertoolsgói.

> pip install more_itertools

Nó vận chuyển với một triển khai cho flatten( nguồn , từ các công thức nấu ăn của 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]

Kể từ phiên bản 2.4, bạn có thể làm phẳng các vòng lặp lồng nhau phức tạp hơn với more_itertools.collapse ( nguồn , được đóng góp bởi 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]

Thật. Đây phải là câu trả lời được chấp nhận
brunetton

Nếu bạn có đủ khả năng để thêm một gói vào dự án của bạn - câu trả lời này là tốt nhất
viddik13

22

Lý do chức năng của bạn không hoạt động là vì phần mở rộng mở rộng một mảng tại chỗ và không trả về nó. Bạn vẫn có thể trả về x từ lambda, bằng cách sử dụng một cái gì đó như thế này:

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

Lưu ý: tiện ích mở rộng hiệu quả hơn + trên danh sách.


7
extendđược tốt hơn sử dụng như newlist = [], extend = newlist.extend, for sublist in l: extend(l)vì nó tránh được (khá lớn) hất của lambda, tra cứu thuộc tính trên x, và or.
agf

cho python 3 thêmfrom 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

Phiên bản đệ quy

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
tốt đẹp, không cần nhập khẩu và rõ ràng những gì nó đang làm ... làm phẳng một danh sách, giai đoạn :)
Goran B.

1
đơn giản rực rỡ!
Sachin Sharma

15

matplotlib.cbook.flatten() sẽ làm việc cho các danh sách lồng nhau ngay cả khi chúng lồng sâu hơn ví dụ.

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

Kết quả:

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

Tốc độ này nhanh hơn 18 lần so với gạch dưới ._. 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

14

Câu trả lời được chấp nhận không hoạt động với tôi khi xử lý các danh sách dựa trên văn bản có độ dài thay đổi. Đây là một cách tiếp cận thay thế đã làm việc cho tôi.

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

Câu trả lời được chấp nhận không hoạt động:

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

Giải pháp đề xuất mới đã làm việc cho tôi:

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

Một tính năng xấu của chức năng Anil ở trên là nó yêu cầu người dùng luôn chỉ định thủ công đối số thứ hai là một danh sách trống []. Điều này thay vào đó nên là một mặc định. Do cách thức hoạt động của các đối tượng Python, chúng nên được đặt bên trong hàm chứ không phải trong các đối số.

Đây là một chức năng làm việc:

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

Kiểm tra:

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

Theo tôi có vẻ đơn giản nhất:

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

Không hoạt động cho danh sách với các kích thước khác nhau. -1
nuôi dưỡng

10

Người ta cũng có thể sử dụng căn hộ NumPy :

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

Chỉnh sửa 11/02/2016: Chỉ hoạt động khi danh sách phụ có kích thước giống hệt nhau.


đó sẽ là giải pháp tối ưu?
RetroCode

6

Bạn có thể sử dụng numpy:
flat_list = list(np.concatenate(list_of_list))


Điều này cũng hoạt động cho các số, chuỗi và danh sách hỗn hợp
Nitin

2
Thất bại cho dữ liệu được lồng không đều, như[1, 2, [3], [[4]], [5, [6]]]
EL_DON

5

Nếu bạn sẵn sàng từ bỏ một tốc độ nhỏ để có vẻ ngoài gọn gàng hơn, thì bạn có thể sử dụng numpy.concatenate().tolist()hoặc 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

Bạn có thể tìm hiểu thêm ở đây trong các tài liệu numpy.concatenatenumpy.ravel


1
Không hoạt động cho các danh sách lồng nhau không đồng đều như[1, 2, [3], [[4]], [5, [6]]]
EL_DON

5

Giải pháp nhanh nhất tôi đã tìm thấy (cho danh sách lớn nào):

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

Làm xong! Tất nhiên bạn có thể biến nó trở lại thành một danh sách bằng cách thực hiện danh sách (l)


1
Điều này là sai, flatten sẽ giảm kích thước của mảng nd thành một, nhưng không nối các danh sách bên trong thành một.
Ando Jurai

5

Mã đơn giản cho underscore.pyquạt gói

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

Nó giải quyết tất cả các vấn đề làm phẳng (không có mục danh sách hoặc lồng phức tạp)

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]

Bạn có thể cài đặt underscore.pyvới pip

pip install underscore.py

Tương tự, bạn có thể sử dụng pydash . Tôi thấy phiên bản này dễ đọc hơn nhiều so với việc hiểu danh sách hoặc bất kỳ câu trả lời nào khác.
gliemezis

2
Đây là siêu chậm.
Nico Schlömer

2
Tại sao nó có một mô-đun có tên _? Đó dường như là một tên xấu. Xem stackoverflow.com/a/5893946/6605826
EL_DON

2
@EL_DON: Từ trang readme underscore.py "Underscore.py là một cổng python của thư viện javascript tuyệt vời underscore.js". Tôi nghĩ đó là lý do cho cái tên này. Và vâng, nó không phải là một cái tên hay cho trăn
Vũ Anh

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

Thất bại cho python2.7 cho danh sách lồng nhau trong câu hỏi:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON

@EL_DON đã thử nghiệm trên python 2.7.5. nó hoạt động tốt
englealuze

5

Lưu ý : Dưới đây áp dụng cho Python 3.3+ vì nó sử dụng yield_from. sixcũng là gói của bên thứ ba, mặc dù nó ổn định. Thay phiên, bạn có thể sử dụng sys.version.


Trong trường hợp obj = [[1, 2,], [3, 4], [5, 6]], tất cả các giải pháp ở đây đều tốt, bao gồm hiểu danh sách vàitertools.chain.from_iterable .

Tuy nhiên, hãy xem xét trường hợp phức tạp hơn một chút này:

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

Có một số vấn đề ở đây:

  • Một yếu tố, 6 chỉ là một vô hướng; nó không lặp lại được, vì vậy các tuyến đường trên sẽ thất bại ở đây.
  • Một yếu tố, 'abc', iterable về mặt kỹ thuật (tất cảstr s là). Tuy nhiên, đọc giữa các dòng một chút, bạn không muốn coi nó như vậy - bạn muốn coi nó như một yếu tố duy nhất.
  • Phần tử cuối cùng, [8, [9, 10]]chính nó là một vòng lặp lồng nhau. Hiểu danh sách cơ bản và chain.from_iterablechỉ trích xuất "1 cấp xuống."

Bạn có thể khắc phục điều này như sau:

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

Tại đây, bạn kiểm tra xem phần tử con (1) có thể lặp lại với Iterable, một ABC từ itertools, nhưng cũng muốn đảm bảo rằng (2) phần tử không "giống như chuỗi".


1
Nếu bạn vẫn còn quan tâm đến Python 2 khả năng tương thích, thay đổi yield fromđến một forvòng lặp, ví dụfor x in flatten(i): yield x
pylang

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

Mã này cũng hoạt động tốt vì nó chỉ mở rộng danh sách tất cả các cách. Mặc dù nó rất giống nhau nhưng chỉ có một vòng lặp. Vì vậy, nó có độ phức tạp ít hơn so với việc thêm 2 cho các vòng lặp.


5
from nltk import flatten

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

Ưu điểm của giải pháp này so với hầu hết các giải pháp khác ở đây là nếu bạn có một danh sách như:

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

trong khi hầu hết các giải pháp khác đưa ra lỗi thì giải pháp này xử lý chúng.


Câu hỏi nêu "danh sách danh sách", nhưng danh sách ví dụ của bạn bao gồm một mục không có trong danh sách. Hầu hết các giải pháp khác đang bám sát câu hỏi ban đầu. Giải pháp của bạn giải quyết vấn đề rộng hơn, nhưng nó cũng yêu cầu gói Python không phải là cơ sở (nltk) phải được cài đặt trước.
simonobo

4

Đây có thể không phải là cách hiệu quả nhất nhưng tôi nghĩ nên đặt một lớp lót (thực ra là hai lớp lót). Cả hai phiên bản sẽ hoạt động trên các danh sách phân cấp lồng nhau tùy ý và khai thác các tính năng ngôn ngữ (Python3.5) và đệ quy.

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)

Đầu ra là

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

Điều này hoạt động theo cách sâu đầu tiên. Đệ quy đi xuống cho đến khi nó tìm thấy một phần tử không có trong danh sách, sau đó mở rộng biến cục bộ flistvà sau đó cuộn nó lại cho cha mẹ. Bất cứ khi nào flistđược trả lại, nó được mở rộng cho phụ huynh flisttrong phần hiểu danh sách. Do đó, tại thư mục gốc, một danh sách phẳng được trả về.

Cái trên tạo ra một số danh sách địa phương và trả về chúng được sử dụng để mở rộng danh sách của cha mẹ. Tôi nghĩ rằng cách xung quanh cho điều này có thể đang tạo ra một sự sáng chói flist, như dưới đây.

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)

Đầu ra là một lần nữa

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

Mặc dù tôi không chắc chắn vào lúc này về hiệu quả.


Tại sao mở rộng ([l]) thay vì nối (l)?
Maciek

3

Một cách tiếp cận khác thường hoạt động cho danh sách các số nguyên không đồng nhất và đồng nhất:

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

Đó chỉ là một cách phức tạp hơn và chậm hơn một chút so với 0003000 đã được đăng trước đó. Tôi đã phát minh lại đề xuất của anh ấy ngày hôm qua, vì vậy cách tiếp cận này có vẻ khá phổ biến những ngày này;)
Darkonaut

Không hoàn toàn: 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

mã của tôi dưới dạng một lớp lót sẽ là: flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
tharndt

1
Bạn thực sự đúng +1, đề xuất của 0003000 sẽ không hoạt động với nhiều chữ số, tôi cũng không kiểm tra điều này trước đây mặc dù điều đó là hiển nhiên. Bạn có thể đơn giản hóa mã của bạn và viết [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. Nhưng tôi đề nghị nên tuân theo đề xuất của Deleet cho các trường hợp sử dụng thực sự. Nó không chứa các biến đổi loại hacky, nó nhanh hơn và linh hoạt hơn bởi vì nó tự nhiên cũng xử lý các danh sách với các loại hỗn hợp.
Darkonaut

2
Tiếc là không có. Nhưng tôi thấy mã này thời gian gần đây ở đây: Python Practice Book 6.1.2
tharndt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.