Python có thể kiểm tra tư cách thành viên của nhiều giá trị trong danh sách không?


121

Tôi muốn kiểm tra xem hai hoặc nhiều giá trị có thành viên trong danh sách hay không, nhưng tôi nhận được kết quả không mong muốn:

>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)

Vì vậy, Python có thể kiểm tra tư cách thành viên của nhiều giá trị cùng một lúc trong một danh sách không? Kết quả đó có ý nghĩa gì?

Câu trả lời:


197

Điều này thực hiện những gì bạn muốn và sẽ hoạt động trong hầu hết các trường hợp:

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True

Biểu thức 'a','b' in ['b', 'a', 'foo', 'bar']không hoạt động như mong đợi vì Python diễn giải nó như một bộ tuple:

>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)

Sự lựa chọn khác

Có nhiều cách khác để thực hiện kiểm tra này, nhưng chúng sẽ không hoạt động với nhiều loại đầu vào khác nhau. Như Kabie đã chỉ ra, bạn có thể giải quyết vấn đề này bằng cách sử dụng bộ ...

>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True

...đôi khi:

>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Bộ chỉ có thể được tạo bằng các phần tử có thể băm. Nhưng biểu thức trình tạo all(x in container for x in items)có thể xử lý hầu hết mọi loại vùng chứa. Yêu cầu duy nhất là có containerthể lặp lại (nghĩa là không phải là trình tạo). itemscó thể được lặp lại bất kỳ.

>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True

Kiểm tra tốc độ

Trong nhiều trường hợp, kiểm tra tập hợp con sẽ nhanh hơn all, nhưng sự khác biệt không gây sốc - ngoại trừ khi câu hỏi không liên quan vì tập hợp không phải là một lựa chọn. Việc chuyển đổi danh sách thành các tập hợp chỉ cho mục đích kiểm tra như thế này sẽ không phải lúc nào cũng đáng gặp rắc rối. Và việc chuyển đổi bộ tạo thành bộ đôi khi có thể vô cùng lãng phí, làm chậm các chương trình theo nhiều mức độ lớn.

Dưới đây là một vài tiêu chuẩn để minh họa. Sự khác biệt lớn nhất đến khi cả hai containeritemstương đối nhỏ. Trong trường hợp đó, cách tiếp cận tập hợp con nhanh hơn về thứ tự cường độ:

>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Điều này trông giống như một sự khác biệt lớn. Nhưng miễn containerlà một bộ, allvẫn hoàn toàn có thể sử dụng được ở quy mô lớn hơn rất nhiều:

>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Sử dụng thử nghiệm tập hợp con vẫn nhanh hơn, nhưng chỉ khoảng 5x ở quy mô này. Việc tăng tốc độ là do ctriển khai dự phòng nhanh của Python set, nhưng thuật toán cơ bản là giống nhau trong cả hai trường hợp.

Nếu của bạn itemsđã được lưu trữ trong danh sách vì những lý do khác, thì bạn sẽ phải chuyển chúng thành một tập hợp trước khi sử dụng phương pháp kiểm tra tập hợp con. Sau đó, tốc độ giảm xuống khoảng 2,5 lần:

>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Và nếu của bạn containerlà một chuỗi và cần được chuyển đổi trước, thì tốc độ tăng thậm chí còn nhỏ hơn:

>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Lần duy nhất chúng ta nhận được kết quả chậm thảm hại là khi chúng ta rời đi containernhư một chuỗi:

>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Và tất nhiên, chúng tôi sẽ chỉ làm điều đó nếu chúng tôi phải làm. Nếu tất cả các mục trong bigseqđều có thể băm, thì chúng tôi sẽ thực hiện việc này thay thế:

>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Nhanh hơn chỉ 1,66 lần so với giải pháp thay thế ( set(bigseq) >= set(bigsubseq), được tính thời gian ở trên là 4,36).

Vì vậy, kiểm tra tập hợp con thường nhanh hơn, nhưng không phải bởi một lợi nhuận đáng kinh ngạc. Mặt khác, hãy xem khi nào allthì nhanh hơn. Điều gì sẽ xảy ra nếu itemsgiá trị mười triệu dài và có khả năng có các giá trị không bằng container?

>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Chuyển đổi máy phát điện thành một bộ hóa ra là vô cùng lãng phí trong trường hợp này. Hàm settạo phải sử dụng toàn bộ bộ tạo. Nhưng hành vi đoản mạch của allđảm bảo rằng chỉ một phần nhỏ của máy phát điện cần được tiêu thụ, vì vậy nó nhanh hơn thử nghiệm tập hợp con bốn bậc độ lớn .

Đây là một ví dụ cực đoan, phải thừa nhận. Nhưng như nó cho thấy, bạn không thể cho rằng cách tiếp cận này hay cách khác sẽ nhanh hơn trong mọi trường hợp.

Ảnh chụp ngược

Hầu hết thời gian, chuyển đổi containerthành một tập hợp là đáng giá, ít nhất là nếu tất cả các phần tử của nó đều có thể băm được. Đó là bởi vì inđối với tập hợp là O (1), trong khi inđối với trình tự là O (n).

Mặt khác, việc sử dụng thử nghiệm tập hợp con có lẽ đôi khi chỉ có giá trị. Chắc chắn làm điều đó nếu các mục thử nghiệm của bạn đã được lưu trữ trong một bộ. Nếu không, allchỉ chậm hơn một chút và không yêu cầu bất kỳ bộ nhớ bổ sung nào. Nó cũng có thể được sử dụng với các bộ tạo vật phẩm lớn, và đôi khi cung cấp một tốc độ lớn trong trường hợp đó.


62

Một cách khác để làm điều đó:

>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True

21
Thực tế thú vị: set(['a', 'b']) <= set(['b','a','foo','bar'])là một cách khác để đánh vần cùng một thứ, và trông "mathier".
Kirk Strauser

8
Tính đến Python 2.7, bạn có thể sử dụng{'a', 'b'} <= {'b','a','foo','bar'}
Viktor Stískala

11

Tôi khá chắc chắn inlà có mức độ ưu tiên cao hơn ,vì vậy câu lệnh của bạn đang được hiểu là 'a', ('b' in ['b' ...]), sau đó đánh giá thành 'a', True'b'nằm trong mảng.

Xem câu trả lời trước để biết cách làm những gì bạn muốn.


7

Nếu bạn muốn kiểm tra tất cả các kết quả phù hợp đầu vào của mình ,

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

nếu bạn muốn kiểm tra ít nhất một trận đấu ,

>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

3

Trình phân tích cú pháp Python đã đánh giá câu lệnh đó như một bộ giá trị, trong đó giá trị đầu tiên là 'a'và giá trị thứ hai là biểu thức 'b' in ['b', 'a', 'foo', 'bar'](đánh giá thành True).

Tuy nhiên, bạn có thể viết một hàm đơn giản để làm những gì bạn muốn:

def all_in(candidates, sequence):
    for element in candidates:
        if element not in sequence:
            return False
    return True

Và gọi nó như:

>>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
True

2
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]

Lý do tôi nghĩ câu này tốt hơn câu trả lời đã chọn là bạn thực sự không cần gọi hàm 'all ()'. Danh sách rỗng đánh giá là Sai trong câu lệnh IF, danh sách không trống đánh giá là Đúng.

if [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]:
    ...Do something...

Thí dụ:

>>> [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
['a', 'b']
>>> [x for x in ['G','F'] if x in ['b', 'a', 'foo', 'bar']]
[]

1

Tôi sẽ nói rằng chúng ta thậm chí có thể bỏ đi những dấu ngoặc vuông đó.

array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])

0

Cả hai câu trả lời được trình bày ở đây sẽ không xử lý các phần tử lặp lại. Ví dụ: nếu bạn đang kiểm tra xem [1,2,2] có phải là danh sách con của [1,2,3,4] hay không, cả hai đều sẽ trả về True. Đó có thể là những gì bạn muốn làm, nhưng tôi chỉ muốn làm rõ. Nếu bạn muốn trả về false cho [1,2,2] trong [1,2,3,4], bạn cần phải sắp xếp cả hai danh sách và kiểm tra từng mục với một chỉ mục di chuyển trên mỗi danh sách. Chỉ là một vòng lặp for phức tạp hơn một chút.


1
'cả hai'? Có nhiều hơn hai câu trả lời. Ý của bạn là tất cả các câu trả lời đều mắc phải vấn đề này, hay chỉ có hai trong số các câu trả lời (và nếu có thì là câu trả lời nào)?
Wipqozn

-1

Làm sao bạn có thể trở thành con trăn nếu không có lambdas! .. không được coi trọng .. nhưng cách này cũng hoạt động:

orig_array = [ ..... ]
test_array = [ ... ]

filter(lambda x:x in test_array, orig_array) == test_array

bỏ phần cuối nếu bạn muốn kiểm tra xem giá trị nào trong mảng không:

filter(lambda x:x in test_array, orig_array)

1
Chỉ cần lưu ý rằng điều này sẽ không hoạt động như dự định trong Python 3, nơi filterlà một trình tạo. Bạn cần phải bọc nó lại listnếu bạn muốn thực sự nhận được kết quả mà bạn có thể kiểm tra với ==hoặc trong ngữ cảnh boolean (để xem nó có trống không). Sử dụng khả năng hiểu danh sách hoặc biểu thức trình tạo trong anyhoặc allthích hợp hơn.
Blckknght

-1

Đây là cách tôi đã làm điều đó:

A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
    do something
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.