Cú pháp thành ngữ để chuẩn bị cho một danh sách python ngắn là gì?


543

list.append()là sự lựa chọn rõ ràng để thêm vào cuối danh sách. Đây là một lời giải thích hợp lý cho sự mất tích list.prepend(). Giả sử danh sách của tôi ngắn và mối quan tâm về hiệu suất là không đáng kể, là

list.insert(0, x)

hoặc là

list[0:0] = [x]

thành ngữ?

Câu trả lời:


784

Các s.insert(0, x)hình thức là phổ biến nhất.

Bất cứ khi nào bạn nhìn thấy nó, có lẽ đã đến lúc cân nhắc sử dụng bộ sưu tập.deque thay vì danh sách.


9
"Bất cứ khi nào bạn nhìn thấy nó, có lẽ đã đến lúc cân nhắc sử dụng bộ sưu tập.deque thay vì danh sách." Tại sao lại thế này?
Matt M.

6
@MattM. Nếu bạn chèn ở phía trước danh sách, python phải di chuyển tất cả các mục khác một khoảng trống về phía trước, danh sách không thể "tạo khoảng trống ở phía trước". bộ sưu tập.deque (hàng đợi kết thúc kép) có hỗ trợ "tạo không gian ở phía trước" và nhanh hơn nhiều trong trường hợp này.
fejfo

265

Nếu bạn có thể đi theo cách chức năng, sau đây là khá rõ ràng

new_list = [x] + your_list

Tất nhiên, bạn chưa chèn xvào your_list, thay vào đó bạn đã tạo một danh sách mới với xviệc đưa nó vào trước.


45
Khi bạn quan sát, đó không phải là một danh sách. Nó đang tạo ra một danh sách mới. Vì vậy, nó không đáp ứng câu hỏi nào cả.
Chris Morgan

112
Mặc dù nó không thỏa mãn câu hỏi, nhưng nó làm tròn ra và đó là mục đích của trang web này. Đánh giá cao nhận xét và bạn đã đúng, nhưng khi mọi người tìm kiếm nó, thật hữu ích khi thấy điều này.
dave4jr

2
Ngoài ra, nếu bạn muốn thêm một danh sách vào danh sách thì sử dụng insert sẽ không hoạt động như mong đợi. nhưng phương pháp này nào!
gota

90

Cú pháp thành ngữ để chuẩn bị cho một danh sách python ngắn là gì?

Bạn thường không muốn lặp đi lặp lại một danh sách trong Python.

Nếu nó ngắn và bạn không làm việc đó nhiều ... thì ok.

list.insert

list.insertthể được sử dụng theo cách này.

list.insert(0, x)

Nhưng điều này là không hiệu quả, bởi vì trong Python, a listlà một mảng các con trỏ và Python bây giờ phải lấy mọi con trỏ trong danh sách và di chuyển nó xuống một để chèn con trỏ vào đối tượng của bạn trong khe đầu tiên, vì vậy điều này thực sự chỉ hiệu quả cho danh sách khá ngắn, như bạn yêu cầu.

Đây là một đoạn trích từ nguồn CPython , nơi điều này được triển khai - và như bạn có thể thấy, chúng tôi bắt đầu ở cuối mảng và di chuyển mọi thứ xuống một cho mỗi lần chèn:

for (i = n; --i >= where; )
    items[i+1] = items[i];

Nếu bạn muốn một thùng chứa / danh sách hiệu quả trong việc chuẩn bị các yếu tố, bạn muốn có một danh sách được liên kết. Python có một danh sách liên kết đôi, có thể chèn vào đầu và cuối một cách nhanh chóng - nó được gọi là a deque.

deque.appendleft

A collections.dequecó nhiều phương pháp của một danh sách. list.sortlà một ngoại lệ, làm cho dequedứt khoát không hoàn toàn thay thế Liskov cho list.

>>> set(dir(list)) - set(dir(deque))
{'sort'}

Các dequecòn có một appendleftphương pháp (cũng như popleft). Đây dequelà một hàng đợi hai đầu và một danh sách liên kết đôi - bất kể độ dài, nó luôn mất cùng một khoảng thời gian để trả trước một cái gì đó. Trong ký hiệu O lớn, O (1) so với thời gian O (n) cho danh sách. Đây là cách sử dụng:

>>> import collections
>>> d = collections.deque('1234')
>>> d
deque(['1', '2', '3', '4'])
>>> d.appendleft('0')
>>> d
deque(['0', '1', '2', '3', '4'])

deque.extendleft

Cũng có liên quan là extendleftphương pháp của deque , mà lặp đi lặp lại:

>>> from collections import deque
>>> d2 = deque('def')
>>> d2.extendleft('cba')
>>> d2
deque(['a', 'b', 'c', 'd', 'e', 'f'])

Lưu ý rằng mỗi phần tử sẽ được đặt trước một phần tử, do đó đảo ngược hiệu quả thứ tự của chúng.

Hiệu suất listso vớideque

Đầu tiên chúng tôi thiết lập với một số lần lặp lại:

import timeit
from collections import deque

def list_insert_0():
    l = []
    for i in range(20):
        l.insert(0, i)

def list_slice_insert():
    l = []
    for i in range(20):
        l[:0] = [i]      # semantically same as list.insert(0, i)

def list_add():
    l = []
    for i in range(20):
        l = [i] + l      # caveat: new list each time

def deque_appendleft():
    d = deque()
    for i in range(20):
        d.appendleft(i)  # semantically same as list.insert(0, i)

def deque_extendleft():
    d = deque()
    d.extendleft(range(20)) # semantically same as deque_appendleft above

và trình diễn:

>>> min(timeit.repeat(list_insert_0))
2.8267281929729506
>>> min(timeit.repeat(list_slice_insert))
2.5210217320127413
>>> min(timeit.repeat(list_add))
2.0641671380144544
>>> min(timeit.repeat(deque_appendleft))
1.5863927800091915
>>> min(timeit.repeat(deque_extendleft))
0.5352169770048931

Deque nhanh hơn nhiều. Khi các danh sách dài hơn, tôi sẽ mong đợi một deque sẽ thực hiện tốt hơn nữa. Nếu bạn có thể sử dụng deque, extendleftcó lẽ bạn sẽ có được hiệu suất tốt nhất theo cách đó.


57

Nếu ai đó tìm thấy câu hỏi này giống tôi, đây là bài kiểm tra hiệu suất của tôi về các phương pháp được đề xuất:

Python 2.7.8

In [1]: %timeit ([1]*1000000).insert(0, 0)
100 loops, best of 3: 4.62 ms per loop

In [2]: %timeit ([1]*1000000)[0:0] = [0]
100 loops, best of 3: 4.55 ms per loop

In [3]: %timeit [0] + [1]*1000000
100 loops, best of 3: 8.04 ms per loop

Như bạn có thể thấy, insertvà phép gán lát nhanh hơn gần gấp đôi so với việc thêm rõ ràng và rất gần với kết quả. Như Raymond Hettinger lưu ý insertlà tùy chọn phổ biến hơn và tôi, cá nhân tôi thích cách này để thêm vào danh sách.


11
Một điều còn thiếu trong bài kiểm tra đó là sự phức tạp. Mặc dù hai tùy chọn đầu tiên có độ phức tạp không đổi (nó không bị chậm hơn khi có nhiều phần tử trong danh sách), nhưng tùy chọn thứ ba có độ phức tạp tuyến tính (nó sẽ chậm hơn, tùy thuộc vào số lượng phần tử trong danh sách), bởi vì nó luôn luôn phải sao chép toàn bộ danh sách. Với nhiều yếu tố trong danh sách, kết quả có thể trở nên tồi tệ hơn rất nhiều.
Dakkaron

6
@Dakkaron Tôi nghĩ bạn đã sai về điều đó. Khá nhiều nguồn trích dẫn độ phức tạp tuyến tính cho list.insert, ví dụ bảng đẹp này và ngụ ý giải thích hợp lý mà người hỏi liên quan đến. Tôi nghi ngờ CPython đang phân bổ lại từng yếu tố trong bộ nhớ trong danh sách trong hai trường hợp đầu tiên, vì vậy cả ba yếu tố này có thể có độ phức tạp tuyến tính. Tôi thực sự đã không nhìn vào mã hoặc tự kiểm tra nó, rất xin lỗi nếu những nguồn đó sai. Bộ sưu tập.deque.appendleft có độ phức tạp tuyến tính mà bạn đang nói đến.
TC Proctor

@Dakkaron không đúng, tất cả đều có độ phức tạp tương đương. Mặc dù .insert[0:0] = [0]làm việc tại chỗ , họ vẫn phải phân bổ lại toàn bộ bộ đệm.
juanpa.arrivillaga

Những điểm chuẩn là xấu. Danh sách ban đầu nên được tạo trong bước thiết lập riêng biệt, không phải là một phần của thời gian. Và cái cuối cùng tạo ra một danh sách mới dài 1000001, vì vậy so sánh với hai phiên bản tại chỗ đột biến khác là táo và cam.
Wim
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.