Câu trả lời ngắn gọn : sử dụng not set(a).isdisjoint(b)
, nói chung là nhanh nhất.
Có bốn cách phổ biến để kiểm tra nếu hai danh sách a
và b
chia sẻ bất kỳ mục nào. Tùy chọn đầu tiên là chuyển đổi cả hai thành tập hợp và kiểm tra giao điểm của chúng, như sau:
bool(set(a) & set(b))
Vì các bộ được lưu trữ bằng bảng băm trong Python, nên việc tìm kiếm chúng làO(1)
(xem tại đây để biết thêm thông tin về độ phức tạp của các toán tử trong Python). Về mặt lý thuyết, đây là O(n+m)
trung bình cho n
và m
các đối tượng trong danh sách a
và b
. Nhưng 1) trước tiên, nó phải tạo ra các bộ trong danh sách, có thể mất một lượng thời gian không đáng kể và 2) nó cho rằng các va chạm băm là thưa thớt giữa các dữ liệu của bạn.
Cách thứ hai để làm điều đó là sử dụng biểu thức trình tạo thực hiện phép lặp trên các danh sách, chẳng hạn như:
any(i in a for i in b)
Điều này cho phép tìm kiếm tại chỗ, do đó không có bộ nhớ mới được phân bổ cho các biến trung gian. Nó cũng bảo lãnh cho lần tìm đầu tiên. Nhưng in
nhà điều hành luôn O(n)
nằm trong danh sách (xem tại đây ).
Một tùy chọn được đề xuất khác là kết hợp lặp lại thông qua một trong các danh sách, chuyển đổi một cái khác trong một bộ và kiểm tra tư cách thành viên trên bộ này, như vậy:
a = set(a); any(i in a for i in b)
Cách tiếp cận thứ tư là tận dụng isdisjoint()
phương pháp của các bộ (đóng băng) (xem tại đây ), ví dụ:
not set(a).isdisjoint(b)
Nếu các phần tử bạn tìm kiếm ở gần đầu của một mảng (ví dụ: nó được sắp xếp), biểu thức trình tạo được ưa thích, vì phương thức giao cắt tập hợp phải phân bổ bộ nhớ mới cho các biến trung gian:
from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974
Dưới đây là biểu đồ về thời gian thực hiện cho ví dụ này với chức năng kích thước danh sách:
Lưu ý rằng cả hai trục là logarit. Điều này đại diện cho trường hợp tốt nhất cho biểu thức máy phát điện. Có thể thấy, isdisjoint()
phương thức này tốt hơn cho các kích thước danh sách rất nhỏ, trong khi biểu thức trình tạo tốt hơn cho các kích thước danh sách lớn hơn.
Mặt khác, khi tìm kiếm bắt đầu bằng bắt đầu cho biểu thức kết hợp và trình tạo, nếu phần tử được chia sẻ có hệ thống ở cuối mảng (hoặc cả hai danh sách không chia sẻ bất kỳ giá trị nào), thì cách tiếp cận giao nhau và đặt giao nhau cách nhanh hơn biểu thức máy phát điện và phương pháp lai.
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668
Thật thú vị khi lưu ý rằng biểu thức trình tạo chậm hơn đối với kích thước danh sách lớn hơn. Điều này chỉ dành cho 1000 lần lặp lại, thay vì 100000 cho con số trước đó. Thiết lập này cũng xấp xỉ tốt khi không có phần tử nào được chia sẻ và là trường hợp tốt nhất cho các cách tiếp cận phân biệt và đặt giao nhau.
Dưới đây là hai phân tích sử dụng các số ngẫu nhiên (thay vì gian lận thiết lập để ưu tiên kỹ thuật này hay kỹ thuật khác):
Cơ hội chia sẻ cao: các yếu tố được lấy ngẫu nhiên từ [1, 2*len(a)]
. Cơ hội chia sẻ thấp: các yếu tố được lấy ngẫu nhiên từ [1, 1000*len(a)]
.
Cho đến nay, phân tích này cho rằng cả hai danh sách có cùng kích thước. Trong trường hợp hai danh sách có kích thước khác nhau, ví dụ a
nhỏ hơn nhiều, isdisjoint()
luôn nhanh hơn:
Hãy chắc chắn rằng a
danh sách là nhỏ hơn, nếu không hiệu suất giảm. Trong thí nghiệm này, a
kích thước danh sách được đặt không đổi 5
.
Tóm tắt:
- Nếu danh sách rất nhỏ (<10 yếu tố),
not set(a).isdisjoint(b)
luôn luôn là nhanh nhất.
- Nếu các phần tử trong danh sách được sắp xếp hoặc có cấu trúc thông thường mà bạn có thể tận dụng, biểu thức trình tạo
any(i in a for i in b)
là nhanh nhất trên các kích thước danh sách lớn;
- Kiểm tra giao điểm thiết lập với
not set(a).isdisjoint(b)
, luôn luôn nhanh hơn bool(set(a) & set(b))
.
- Phép lai "lặp qua danh sách, kiểm tra trên tập"
a = set(a); any(i in a for i in b)
thường chậm hơn các phương thức khác.
- Biểu thức trình tạo và kết hợp chậm hơn nhiều so với hai cách tiếp cận khác khi nói đến danh sách mà không chia sẻ các phần tử.
Trong hầu hết các trường hợp, sử dụng isdisjoint()
phương thức là cách tiếp cận tốt nhất vì biểu thức trình tạo sẽ mất nhiều thời gian hơn để thực thi, vì nó rất không hiệu quả khi không có phần tử nào được chia sẻ.
len(...) > 0
vìbool(set([]))
mang lại Sai. Và tất nhiên, nếu bạn giữ danh sách của mình dưới dạng các bộ để bắt đầu, bạn sẽ tiết kiệm chi phí tạo bộ.