Kiểm tra nếu danh sách chia sẻ bất kỳ mục nào trong python


131

Tôi muốn kiểm tra xem có bất kỳ mục nào trong một danh sách có trong danh sách khác không. Tôi có thể làm điều đó đơn giản với mã bên dưới, nhưng tôi nghi ngờ có thể có chức năng thư viện để làm việc này. Nếu không, có một phương pháp pythonic hơn để đạt được kết quả tương tự.

In [78]: a = [1, 2, 3, 4, 5]

In [79]: b = [8, 7, 6]

In [80]: c = [8, 7, 6, 5]

In [81]: def lists_overlap(a, b):
   ....:     for i in a:
   ....:         if i in b:
   ....:             return True
   ....:     return False
   ....: 

In [82]: lists_overlap(a, b)
Out[82]: False

In [83]: lists_overlap(a, c)
Out[83]: True

In [84]: def lists_overlap2(a, b):
   ....:     return len(set(a).intersection(set(b))) > 0
   ....: 

Tối ưu hóa duy nhất tôi có thể nghĩ là giảm len(...) > 0bool(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ộ.
msw


1
Lưu ý rằng bạn không thể phân biệt Truetừ 1Falsetừ 0. not set([1]).isdisjoint([True])được True, cùng với các giải pháp khác.
Dimali

Câu trả lời:


313

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

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 nmcác đối tượng trong danh sách ab. 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 innhà đ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:

Thời gian thực hiện kiểm tra chia sẻ phần tử khi được chia sẻ ở đầu

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ời gian thực hiện kiểm tra chia sẻ phần tử khi được chia sẻ ở cuối

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

Thời gian thực hiện kiểm tra chia sẻ phần tử cho dữ liệu được tạo ngẫu nhiên có cơ hội chia sẻ cao Thời gian thực hiện kiểm tra chia sẻ phần tử cho dữ liệu được tạo ngẫu nhiên có cơ hội chia sẻ cao

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ụ anhỏ hơn nhiều, isdisjoint()luôn nhanh hơn:

Thời gian thực hiện kiểm tra chia sẻ phần tử trên hai danh sách có kích thước khác nhau khi được chia sẻ ở đầu Thời gian thực hiện kiểm tra chia sẻ phần tử trên hai danh sách có kích thước khác nhau khi được chia sẻ ở cuối

Hãy chắc chắn rằng adanh sách là nhỏ hơn, nếu không hiệu suất giảm. Trong thí nghiệm này, akí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ẻ.


8
Đó là một số dữ liệu hữu ích ở đó, cho thấy phân tích big-O không phải là tất cả và kết thúc tất cả lý do về thời gian chạy.
Steve Allison

trường hợp xấu nhất thì sao? anythoát ra ở giá trị không sai đầu tiên. Sử dụng một danh sách có giá trị khớp duy nhất ở cuối, chúng ta sẽ nhận được điều này: 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 ... và chỉ với 1000 lần lặp.
RobM

2
Cảm ơn @RobM về thông tin. Tôi đã cập nhật câu trả lời của mình để phản ánh điều này và tính đến các kỹ thuật khác được đề xuất trong chuỗi này.
Soravux

Nó sẽ là not set(a).isdisjoint(b)để kiểm tra nếu hai danh sách chia sẻ một thành viên. set(a).isdisjoint(b)trả về Truenếu hai danh sách không chia sẻ một thành viên. Câu trả lời nên được chỉnh sửa?
Guillhol

1
Cảm ơn vì đã đề phòng, @Guillhol, nó đã được sửa.
Soravux

25
def lists_overlap3(a, b):
    return bool(set(a) & set(b))

Lưu ý: các giả định ở trên cho rằng bạn muốn boolean là câu trả lời. Nếu tất cả những gì bạn cần là một biểu thức để sử dụng trong một ifcâu lệnh, chỉ cần sử dụngif set(a) & set(b):


5
Đây là trường hợp xấu nhất O (n + m). Tuy nhiên, mặt trái là nó tạo ra một bộ mới và không cứu trợ khi một yếu tố chung được tìm thấy sớm.
Matthew Flaschen

1
Tôi tò mò về lý do tại sao điều này là O(n + m). Tôi đoán là các tập hợp được triển khai bằng bảng băm và do đó intoán tử có thể hoạt động O(1)kịp thời (trừ trường hợp suy biến). Điều này có đúng không? Nếu vậy, do các bảng băm có hiệu suất tra cứu trường hợp xấu nhất O(n), điều này có nghĩa là trong trường hợp không giống như trường hợp xấu hơn, nó sẽ có O(n * m)hiệu suất?
đánh dấu

1
@fmark: Về mặt lý thuyết, bạn đã đúng. Thực tế, không ai quan tâm; đọc các bình luận trong Object / dictobject.c trong nguồn CPython (các bộ chỉ là các ký tự chỉ có các khóa, không có giá trị) và xem liệu bạn có thể tạo danh sách các khóa sẽ gây ra hiệu suất tra cứu O (n) không.
John Machin

Được rồi, cảm ơn vì đã làm rõ, tôi đã tự hỏi nếu có một số phép thuật đang xảy ra :). Mặc dù tôi đồng ý rằng thực tế tôi không cần phải quan tâm, việc tạo ra một danh sách các khóa sẽ gây ra O(n)hiệu suất tra cứu;), xem pastebin.com/Kn3kAW7u chỉ dành cho lafs.
đánh dấu

2
Vâng, tôi biết. Thêm vào đó, tôi vừa đọc nguồn mà bạn đã chỉ cho tôi, tài liệu này thậm chí còn kỳ diệu hơn trong trường hợp các hàm băm không ngẫu nhiên (chẳng hạn như hàm tích hợp). Tôi giả định rằng nó đòi hỏi sự ngẫu nhiên, giống như Java, dẫn đến sự quái dị như stackoverflow.com/questions/2634690/ này . Tôi cần phải nhắc nhở bản thân rằng Python không phải là Java (cảm ơn vị thần!).
đánh dấu

10
def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)

Đây là tối ưu không có triệu chứng (trường hợp xấu nhất O (n + m)) và có thể tốt hơn phương pháp giao cắt do anychập điện.

Ví dụ:

lists_overlap([3,4,5], [1,2,3])

sẽ trở lại đúng ngay khi nó đến 3 in sb

EDIT: Một biến thể khác (với lời cảm ơn của Dave Kirby):

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))

Điều này phụ thuộc vào imapiterator, được thực hiện trong C, chứ không phải là sự hiểu biết về trình tạo. Nó cũng sử dụng sb.__contains__như chức năng ánh xạ. Tôi không biết sự khác biệt hiệu suất này làm cho bao nhiêu. Nó vẫn sẽ bị đoản mạch.


1
Các vòng lặp trong cách tiếp cận giao lộ đều nằm trong mã C; có một vòng lặp trong cách tiếp cận của bạn bao gồm mã Python. Một ẩn số lớn là liệu một giao lộ trống có khả năng hay không thể xảy ra.
John Machin

2
Bạn cũng có thể sử dụng any(itertools.imap(sb.__contains__, a))cái nào sẽ nhanh hơn vì nó tránh sử dụng hàm lambda.
Dave Kirby

Cảm ơn, @Dave. :) Tôi đồng ý loại bỏ lambda là một chiến thắng.
Matthew Flaschen

4

Bạn cũng có thể sử dụng anyvới việc hiểu danh sách:

any([item in a for item in b])

6
Bạn có thể, nhưng thời gian là O (n * m) trong khi thời gian cho phương pháp giao cắt đã đặt là O (n + m). Bạn cũng có thể làm điều đó mà KHÔNG hiểu danh sách (mất []) và nó sẽ chạy nhanh hơn và sử dụng ít bộ nhớ hơn, nhưng thời gian vẫn là O (n * m).
John Machin

1
Mặc dù phân tích O lớn của bạn là đúng, tôi nghi ngờ rằng đối với các giá trị nhỏ của n và m thì cần có thời gian để xây dựng các hashtag cơ bản sẽ phát huy tác dụng. Big O bỏ qua thời gian cần thiết để tính toán các giá trị băm.
Anthony Conyer

2
Xây dựng "hashtable" được khấu hao O (n).
John Machin

1
Tôi hiểu điều đó nhưng hằng số bạn vứt đi là khá lớn. Nó không quan trọng đối với các giá trị lớn của n, nhưng nó không quan trọng đối với các giá trị nhỏ.
Anthony Conyer

3

Trong python 2.6 trở lên, bạn có thể làm:

return not frozenset(a).isdisjoint(frozenset(b))

1
Có vẻ như người ta không phải cung cấp một tập hợp hoặc frozenet làm đối số đầu tiên. Tôi đã thử với một chuỗi và nó đã hoạt động (tức là: bất kỳ lần lặp nào cũng sẽ làm được).
Aktau

2

Bạn có thể sử dụng bất kỳ biểu thức hàm tạo / wa dựng sẵn nào:

def list_overlap(a,b): 
     return any(i for i in a if i in b)

Như John và Lie đã chỉ ra điều này cho kết quả không chính xác khi cứ mỗi i được chia sẻ bởi hai danh sách bool (i) == Sai. Nó nên là:

return any(i in b for i in a)

1
Sửa đổi nhận xét của Lie Ryan: sẽ đưa ra kết quả sai cho bất kỳ mục x nào trong giao lộ có bool(x)Sai. Trong ví dụ của Lie Ryan, x là 0. Chỉ sửa lỗi được any(True for i in a if i in b)viết tốt hơn như đã thấy any(i in b for i in a).
John Machin

1
Correction: sẽ cho kết quả sai khi tất cả các mặt hàng xtrong các giao lộ là như vậy mà bool(x)False.
John Machin

1

Câu hỏi này khá cũ, nhưng tôi nhận thấy rằng trong khi mọi người đang tranh cãi về các bộ so với danh sách, thì không ai nghĩ đến việc sử dụng chúng cùng nhau. Theo ví dụ của Soravux,

Trường hợp xấu nhất cho danh sách:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234

Và trường hợp tốt nhất cho danh sách:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707

Vì vậy, thậm chí còn nhanh hơn việc lặp qua hai danh sách đang lặp qua một danh sách để xem nó có trong một tập hợp hay không, điều này có ý nghĩa vì việc kiểm tra xem một số có trong một tập hợp có mất thời gian không đổi trong khi kiểm tra bằng cách lặp qua một danh sách sẽ mất thời gian tỷ lệ thuận với độ dài danh sách.

Vì vậy, kết luận của tôi là lặp đi lặp lại qua một danh sách và kiểm tra xem nó có trong một tập hợp không .


1
Sử dụng isdisjoint()phương thức trên một bộ (đóng băng) như được chỉ định bởi @Toughy thậm chí còn tốt hơn: timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)=> 0,00913715362548828
Aktau

1

nếu bạn không quan tâm yếu tố chồng chéo có thể là gì, bạn chỉ cần kiểm tra lendanh sách kết hợp so với danh sách được kết hợp thành một tập hợp. Nếu có các phần tử chồng lấp, tập hợp sẽ ngắn hơn:

len(set(a+b+c))==len(a+b+c) trả về True, nếu không có sự trùng lặp.


Nếu giá trị đầu tiên trùng lặp, nó vẫn sẽ chuyển đổi toàn bộ danh sách thành một tập hợp, bất kể lớn đến đâu.
Peter Wood

1

Tôi sẽ ném một cái khác với phong cách lập trình chức năng:

any(map(lambda x: x in a, b))

Giải trình:

map(lambda x: x in a, b)

trả về một danh sách các booleans nơi các phần tử bđược tìm thấy trong a. Danh sách đó sau đó được chuyển đến any, đơn giản là trả về Truenếu có bất kỳ phần tử nào True.

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.