Python: List vs Dict để tra cứu bảng


169

Tôi có khoảng 10 triệu giá trị mà tôi cần phải đặt trong một số loại nhìn lên bảng, vì vậy tôi đã tự hỏi đó sẽ là một hiệu quả hơn danh sách hoặc dict ?

Tôi biết bạn có thể làm một cái gì đó như thế này cho cả hai:

if something in dict_of_stuff:
    pass

if something in list_of_stuff:
    pass

Tôi nghĩ rằng dict sẽ nhanh hơn và hiệu quả hơn.

Cảm ơn bạn đã giúp đỡ.

EDIT 1
Thông tin thêm về những gì tôi đang cố gắng làm. Bài toán Euler 92 . Tôi đang tạo một bảng tra cứu để xem liệu một giá trị được tính đã sẵn sàng chưa.

EDIT 2
Hiệu quả để tra cứu.

EDIT 3
Không có giá trị nào được xác định bằng giá trị ... vậy một tập hợp sẽ tốt hơn?


1
Hiệu quả về mặt gì? Chèn? Tra cứu? Tiêu thụ bộ nhớ? Bạn đang kiểm tra sự tồn tại thuần túy của giá trị, hay có siêu dữ liệu nào liên quan đến nó không?
truppo

Là một lưu ý phụ, bạn không cần một danh sách 10 triệu hoặc chính tả cho vấn đề cụ thể đó mà là một danh sách nhỏ hơn nhiều.
sfotiadis

Câu trả lời:


222

Tốc độ

Tra cứu trong danh sách là O (n), tra cứu trong từ điển được khấu hao O (1), liên quan đến số lượng mục trong cấu trúc dữ liệu. Nếu bạn không cần liên kết các giá trị, hãy sử dụng các bộ.

Ký ức

Cả từ điển và bộ đều sử dụng băm và chúng sử dụng nhiều bộ nhớ hơn chỉ để lưu trữ đối tượng. Theo AM Kuchling trong Beautiful Code , việc triển khai cố gắng giữ băm 2/3 đầy đủ, do đó bạn có thể lãng phí khá nhiều bộ nhớ.

Nếu bạn không thêm các mục mới một cách nhanh chóng (mà bạn làm, dựa trên câu hỏi được cập nhật của bạn), có thể đáng để sắp xếp danh sách và sử dụng tìm kiếm nhị phân. Đây là O (log n) và có khả năng chậm hơn đối với các chuỗi, không thể đối với các đối tượng không có thứ tự tự nhiên.


6
Có, nhưng đó là hoạt động một lần nếu nội dung không bao giờ thay đổi. Tìm kiếm nhị phân là O (log n).
Torsten Marek

1
@John Fouhy: các int không được lưu trong bảng băm, chỉ các con trỏ, tức là hou có 40M cho các int (tốt, không thực sự khi rất nhiều trong số chúng nhỏ) và 60M cho bảng băm. Tôi đồng ý rằng ngày nay nó không còn là vấn đề nữa, vẫn đáng để ghi nhớ.
Torsten Marek

2
Đây là một câu hỏi cũ, nhưng tôi nghĩ rằng khấu hao O (1) có thể không đúng với các bộ / dicts rất lớn. Trường hợp xấu nhất theo wiki.python.org/moin/TimeComplexity là O (n). Tôi đoán nó phụ thuộc vào việc thực hiện băm nội bộ tại thời điểm trung bình phân kỳ từ O (1) và bắt đầu hội tụ trên O (n). Bạn có thể giúp hiệu suất tra cứu bằng cách sắp xếp các bộ toàn cầu thành các phần nhỏ hơn dựa trên một số thuộc tính dễ nhận thấy (như giá trị của chữ số đầu tiên, sau đó là thứ hai, thứ ba, v.v., miễn là bạn cần có kích thước bộ tối ưu) .
Nisan.H

3
@TorstenMarek Điều này làm tôi bối rối. Từ trang này , tra cứu danh sách là O (1) và tra cứu dict là O (n), điều này trái ngược với những gì bạn nói. Tôi có hiểu lầm không?
tạm

3
@Aerovistae Tôi nghĩ bạn đã đọc sai thông tin trên trang đó. Trong danh sách, tôi thấy O (n) cho "x in s" (tra cứu). Nó cũng hiển thị tra cứu tập hợp và chính tả là trường hợp trung bình O (1).
Dennis

45

Một dict là một bảng băm, vì vậy nó rất nhanh để tìm các khóa. Vì vậy, giữa dict và list, dict sẽ nhanh hơn. Nhưng nếu bạn không có giá trị để liên kết, sử dụng một bộ thậm chí còn tốt hơn. Nó là một bảng băm, không có phần "bảng".


EDIT: cho câu hỏi mới của bạn, CÓ, một bộ sẽ tốt hơn. Chỉ cần tạo 2 bộ, một cho các chuỗi kết thúc bằng 1 và một cho các chuỗi kết thúc vào năm 89. Tôi đã giải quyết thành công vấn đề này bằng cách sử dụng các bộ.


35

set()chính xác là những gì bạn muốn. O (1) tra cứu, và nhỏ hơn một lệnh.


31

Tôi đã thực hiện một số điểm chuẩn và hóa ra dict nhanh hơn cả danh sách và được đặt cho các tập dữ liệu lớn, chạy python 2.7.3 trên CPU i7 trên linux:

  • python -mtimeit -s 'd=range(10**7)' '5*10**6 in d'

    10 vòng, tốt nhất là 3: 64,2 msec mỗi vòng

  • python -mtimeit -s 'd=dict.fromkeys(range(10**7))' '5*10**6 in d'

    10000000 vòng, tốt nhất là 3: 0,0759 usec mỗi vòng

  • python -mtimeit -s 'from sets import Set; d=Set(range(10**7))' '5*10**6 in d'

    1000000 vòng, tốt nhất là 3: 0,262 usec mỗi vòng

Như bạn có thể thấy, dict nhanh hơn đáng kể so với danh sách và nhanh hơn khoảng 3 lần so với thiết lập. Tuy nhiên, trong một số ứng dụng, bạn vẫn có thể muốn chọn thiết lập cho vẻ đẹp của nó. Và nếu các tập dữ liệu thực sự nhỏ (<1000 phần tử) thì danh sách hoạt động khá tốt.


Không phải nó hoàn toàn ngược lại sao? Danh sách: 10 * 64.2 * 1000 = 642000 usec, dict: 10000000 * 0.0759 = 759000 usec và đặt: 1000000 * 0.262 = 262000 usec ... vì vậy, các bộ là nhanh nhất, theo sau là danh sách và với dict như cuối cùng trong ví dụ của bạn. Hay tôi đang thiếu một cái gì đó?
andzep

1
... nhưng câu hỏi cho tôi ở đây là: thời điểm này thực sự đang đo lường cái gì? Không phải thời gian truy cập cho một danh sách, dict hoặc set nhất định, mà nhiều hơn nữa, thời gian và các vòng lặp để tạo danh sách, dict, set và cuối cùng là tìm và truy cập một giá trị. Vì vậy, điều này có liên quan đến câu hỏi không? ... Mặc dù thật thú vị ...
andzep

8
@andzep, bạn nhầm rồi, -stùy chọn là thiết lập timeitmôi trường, tức là nó không được tính vào tổng thời gian. Các -stùy chọn được chỉ chạy một lần. Trên Python 3.3, tôi nhận được các kết quả này: gen (phạm vi) -> 0.229 usec, danh sách -> 157 msec, dict -> 0,0806 usec, set -> 0,0807 usec. Đặt và dict hiệu suất là như nhau. Tuy nhiên, Dict mất nhiều thời gian hơn để khởi tạo so với thiết lập (tổng thời gian 13,580 giây v. 11,80s)
sleblanc

1
Tại sao không sử dụng bộ dựng sẵn? Tôi thực sự nhận được kết quả tồi tệ hơn nhiều với bộ.Set () so với bộ dựng sẵn ()
Thomas Guyot-Sionnest

2
@ ThomasGuyot-Sionnest Bộ dựng sẵn được giới thiệu trong python 2.4 vì vậy tôi không chắc tại sao tôi không sử dụng nó trong giải pháp đề xuất của mình. Tôi có hiệu suất tốt khi python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"sử dụng Python 3.6.0 (10000000 vòng lặp, tốt nhất là 3: 0,0608 usec mỗi vòng lặp), gần giống với điểm chuẩn dict vì vậy cảm ơn bạn đã nhận xét.
EriF89

6

Bạn muốn một lệnh.

Đối với danh sách (chưa được sắp xếp) trong Python, thao tác "trong" yêu cầu thời gian O (n) --- không tốt khi bạn có một lượng lớn dữ liệu. Mặt khác, một dict là một bảng băm, vì vậy bạn có thể mong đợi thời gian tra cứu O (1).

Như những người khác đã lưu ý, thay vào đó, bạn có thể chọn một bộ (một loại chính tả đặc biệt), nếu bạn chỉ có các khóa thay vì các cặp khóa / giá trị.

Liên quan:

  • Python wiki : thông tin về độ phức tạp thời gian của các hoạt động chứa Python.
  • SO : Thời gian hoạt động của bộ chứa Python và độ phức tạp bộ nhớ

1
Ngay cả đối với các danh sách được sắp xếp, "trong" là O (n).

2
Đối với danh sách được liên kết, có --- nhưng "danh sách" trong Python là thứ mà hầu hết mọi người sẽ gọi là vectơ, cung cấp quyền truy cập được lập chỉ mục trong O (1) và thao tác tìm trong O (log n), khi được sắp xếp.
zweiterlinde

Bạn có nói rằng intoán tử được áp dụng cho một danh sách được sắp xếp hoạt động tốt hơn so với khi được áp dụng cho một danh sách chưa được sắp xếp (để tìm kiếm một giá trị ngẫu nhiên)? (Tôi không nghĩ liệu chúng được triển khai bên trong dưới dạng vectơ hay như các nút trong danh sách liên kết có liên quan hay không.)
martineau

4

nếu dữ liệu là tập duy nhất () sẽ hiệu quả nhất, nhưng là hai - dict (cũng đòi hỏi tính duy nhất, rất tiếc :)


Tôi đã nhận ra khi thấy câu trả lời của mình được đăng%)
SilentGhost

2
@SilentGhost nếu câu trả lời sai, tại sao không xóa nó? quá tệ cho các upvote, nhưng điều đó xảy ra (tốt, đã xảy ra )
Jean-François Fabre

3

Như một bộ thử nghiệm mới để hiển thị @ EriF89 vẫn đúng sau tất cả những năm này:

$ python -m timeit -s "l={k:k for k in xrange(5000)}"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.84 msec per loop
$ python -m timeit -s "l=[k for k in xrange(5000)]"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 573 msec per loop
$ python -m timeit -s "l=tuple([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
10 loops, best of 3: 587 msec per loop
$ python -m timeit -s "l=set([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
1000 loops, best of 3: 1.88 msec per loop

Ở đây chúng tôi cũng so sánh a tuple, được biết là nhanh hơn lists(và sử dụng ít bộ nhớ hơn) trong một số trường hợp sử dụng. Trong trường hợp bảng tra cứu, tuplefaired không tốt hơn.

Cả dictsetthực hiện rất tốt. Điều này mang đến một điểm thú vị khi đưa vào câu trả lời @SilentGhost về tính duy nhất: nếu OP có 10M giá trị trong một tập dữ liệu và không biết có trùng lặp trong đó hay không, thì sẽ đáng để giữ song song một tập hợp / các phần tử của nó với tập dữ liệu thực tế và kiểm tra sự tồn tại trong tập / dict đó. Có thể các điểm dữ liệu 10 triệu chỉ có 10 giá trị duy nhất, đó là một không gian nhỏ hơn nhiều để tìm kiếm!

Sai lầm của SilentGhost về các dicts thực sự đang phát sáng vì người ta có thể sử dụng một lệnh để tương quan dữ liệu trùng lặp (trong các giá trị) thành một tập hợp không trùng lặp (các khóa) và do đó giữ một đối tượng dữ liệu để giữ tất cả dữ liệu, nhưng vẫn nhanh như một bảng tra cứu. Ví dụ: khóa dict có thể là giá trị được tra cứu và giá trị có thể là danh sách các chỉ mục trong danh sách tưởng tượng nơi giá trị đó xảy ra.

Ví dụ: nếu danh sách dữ liệu nguồn cần tìm kiếm l=[1,2,3,1,2,1,4], nó có thể được tối ưu hóa cho cả tìm kiếm và bộ nhớ bằng cách thay thế nó bằng lệnh này:

>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> l=[1,2,3,1,2,1,4]
>>> for i, e in enumerate(l):
...     d[e].append(i)
>>> d
defaultdict(<class 'list'>, {1: [0, 3, 5], 2: [1, 4], 3: [2], 4: [6]})

Với chính tả này, người ta có thể biết:

  1. Nếu một giá trị nằm trong tập dữ liệu gốc (nghĩa là 2 in dtrả về True)
  2. Trường hợp giá trị nằm trong tập dữ liệu gốc (nghĩa là d[2]trả về danh sách các chỉ mục nơi dữ liệu được tìm thấy trong danh sách dữ liệu gốc [1, 4]:)

Đối với đoạn cuối cùng của bạn, trong khi nó có ý nghĩa khi đọc nó, nó sẽ rất hay (và có thể dễ nắm bắt hơn) để xem mã thực tế mà bạn đang cố gắng giải thích.
kaiser

0

Bạn thực sự không cần lưu trữ 10 triệu giá trị trong bảng, vì vậy đó cũng không phải là vấn đề lớn.

Gợi ý: suy nghĩ về kết quả của bạn có thể lớn đến mức nào sau tổng hoạt động bình phương đầu tiên. Kết quả lớn nhất có thể sẽ nhỏ hơn 10 triệu ...

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.