Bởi vì danh sách có thể thay đổi, dict
các khóa (và set
thành viên) cần phải có thể băm và việc băm các đối tượng có thể thay đổi là một ý tưởng tồi vì các giá trị băm phải được tính toán dựa trên các thuộc tính cá thể.
Trong câu trả lời này, tôi sẽ đưa ra một số ví dụ cụ thể, hy vọng sẽ tăng thêm giá trị cho các câu trả lời hiện có. Mọi thông tin chi tiết cũng áp dụng cho các phần tử của cơ cấu dữ liệu set
.
Ví dụ 1 : băm một đối tượng có thể thay đổi trong đó giá trị băm dựa trên một đặc tính có thể thay đổi của đối tượng.
>>> class stupidlist(list):
... def __hash__(self):
... return len(self)
...
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True
Sau khi đột biến stupid
, nó không thể được tìm thấy trong dict nữa vì hàm băm đã thay đổi. Chỉ quét tuyến tính trên danh sách các khóa của dict tìm thấy stupid
.
Ví dụ 2 : ... nhưng tại sao không chỉ là một giá trị băm không đổi?
>>> class stupidlist2(list):
... def __hash__(self):
... return id(self)
...
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>>
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False
Đó cũng không phải là một ý kiến hay vì các đối tượng bằng nhau nên băm giống hệt nhau để bạn có thể tìm thấy chúng trong dấu dict
hoặc set
.
Ví dụ 3 : ... ok, còn hàm băm liên tục trên tất cả các trường hợp thì sao ?!
>>> class stupidlist3(list):
... def __hash__(self):
... return 1
...
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>>
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True
Mọi thứ dường như hoạt động như mong đợi, nhưng hãy nghĩ về những gì đang xảy ra: khi tất cả các trường hợp của lớp của bạn tạo ra cùng một giá trị băm, bạn sẽ có xung đột băm bất cứ khi nào có nhiều hơn hai trường hợp làm khóa trong a dict
hoặc hiện tại trong a set
.
Việc tìm kiếm trường hợp phù hợp với my_dict[key]
hoặc key in my_dict
(hoặc item in my_set
) cần thực hiện nhiều lần kiểm tra tính bình đẳng như các trường hợp stupidlist3
trong các khóa của dict (trong trường hợp xấu nhất). Tại thời điểm này, mục đích của từ điển - tra cứu O (1) - hoàn toàn bị đánh bại. Điều này được chứng minh trong thời gian sau (thực hiện với IPython).
Một số thời gian cho Ví dụ 3
>>> lists_list = [[i] for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>>
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Như bạn có thể thấy, kiểm tra tư cách thành viên của chúng tôi stupidlists_set
thậm chí còn chậm hơn so với quét tuyến tính trên toàn bộ lists_list
, trong khi bạn có thời gian tra cứu siêu nhanh dự kiến (hệ số 500) trong một tập hợp mà không có nhiều va chạm băm.
TL; DR: bạn có thể sử dụng tuple(yourlist)
làm dict
khóa, vì các bộ giá trị không thể thay đổi và có thể băm.