Cách tốt nhất để tìm giao điểm của nhiều bộ?


267

Tôi có một danh sách các bộ:

setlist = [s1,s2,s3...]

Tôi muốn s1 ∩ s2 s3 ...

Tôi có thể viết một hàm để làm điều đó bằng cách thực hiện một loạt các cặp s1.intersection(s2), v.v.

Có một cách được đề nghị, tốt hơn, hoặc tích hợp?

Câu trả lời:


454

Từ Python phiên bản 2.6 trở đi, bạn có thể sử dụng nhiều đối số để set.intersection(), như

u = set.intersection(s1, s2, s3)

Nếu các bộ nằm trong một danh sách, điều này có nghĩa là:

u = set.intersection(*setlist)

nơi *a_listMở rộng danh mục

Lưu ý rằng set.intersectionkhông một phương pháp tĩnh, nhưng điều này sử dụng các ký hiệu chức năng để áp dụng giao điểm của tập đầu tiên với phần còn lại của danh sách. Vì vậy, nếu danh sách đối số trống, điều này sẽ thất bại.


65

Kể từ 2.6, set.intersectioncó nhiều lần lặp tùy ý.

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s3 = set([2, 4, 6])
>>> s1 & s2 & s3
set([2])
>>> s1.intersection(s2, s3)
set([2])
>>> sets = [s1, s2, s3]
>>> set.intersection(*sets)
set([2])

24

Rõ ràng set.intersectionlà những gì bạn muốn ở đây, nhưng trong trường hợp bạn cần một sự khái quát hóa "lấy tổng của tất cả những thứ này", "lấy sản phẩm của tất cả những thứ này", "lấy xor của tất cả những thứ này", thứ bạn đang tìm kiếm là reducechức năng:

from operator import and_
from functools import reduce
print(reduce(and_, [{1,2,3},{2,3,4},{3,4,5}])) # = {3}

hoặc là

print(reduce((lambda x,y: x&y), [{1,2,3},{2,3,4},{3,4,5}])) # = {3}

12

Nếu bạn không có Python 2.6 trở lên, cách khác là viết một vòng lặp rõ ràng cho vòng lặp:

def set_list_intersection(set_list):
  if not set_list:
    return set()
  result = set_list[0]
  for s in set_list[1:]:
    result &= s
  return result

set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print set_list_intersection(set_list)
# Output: set([1])

Bạn cũng có thể sử dụng reduce:

set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print reduce(lambda s1, s2: s1 & s2, set_list)
# Output: set([1])

Tuy nhiên, nhiều lập trình viên Python không thích nó, bao gồm cả chính Guido :

Khoảng 12 năm trước, Python ngậm lambda, giảm (), bộ lọc () và bản đồ (), nhờ (tôi tin) một hacker Lisp đã bỏ lỡ chúng và gửi các bản vá hoạt động. Nhưng, mặc dù có giá trị PR, tôi nghĩ các tính năng này nên được cắt từ Python 3000.

Vì vậy, bây giờ giảm (). Đây thực sự là điều tôi luôn ghét nhất, bởi vì, ngoài một vài ví dụ liên quan đến + hoặc *, hầu như mỗi khi tôi thấy một cuộc gọi rút gọn () với một đối số chức năng không tầm thường, tôi cần lấy bút và giấy để sơ đồ những gì thực sự được đưa vào hàm đó trước khi tôi hiểu việc giảm () phải làm là gì. Vì vậy, trong suy nghĩ của tôi, khả năng áp dụng của less () bị giới hạn khá nhiều đối với các toán tử liên kết và trong tất cả các trường hợp khác, tốt hơn hết là viết ra vòng lặp tích lũy một cách rõ ràng.


8
Lưu ý rằng Guido nói rằng việc sử dụng reducelà "giới hạn đối với các nhà khai thác liên kết", áp dụng trong trường hợp này. reducethường rất khó để tìm ra, nhưng &không quá tệ.
Mike Graham


Kiểm tra python.org/doc/essays/list2str để biết các tối ưu hóa hữu ích liên quan đến việc giảm. Nói chung, nó có thể được sử dụng khá độc đáo để xây dựng danh sách, bộ, chuỗi, v.v ... Đáng xem cũng là github.com/EntilZha/PyFeftal
Andreas

Lưu ý bạn có thể tối ưu hóa bằng cách ngắt vòng lặp khi resulttrống.
bfontaine

1

Ở đây tôi đang cung cấp một hàm chung cho giao lộ nhiều bộ đang cố gắng tận dụng phương thức tốt nhất hiện có:

def multiple_set_intersection(*sets):
    """Return multiple set intersection."""
    try:
        return set.intersection(*sets)
    except TypeError: # this is Python < 2.6 or no arguments
        pass

    try: a_set= sets[0]
    except IndexError: # no arguments
        return set() # return empty set

    return reduce(a_set.intersection, sets[1:])

Guido có thể không thích reduce, nhưng tôi thích nó :)


Bạn nên kiểm tra độ dài setsthay vì cố gắng truy cập sets[0]và bắt IndexError.
bfontaine

Đây không phải là một kiểm tra đơn giản; a_setđược sử dụng ở lợi nhuận cuối cùng.
tzot

Bạn không thể làm gì return reduce(sets[0], sets[1:]) if sets else set()?
bfontaine

Hà vâng, cảm ơn bạn. Mã sẽ thay đổi vì dựa vàotry / exceptnên tránh nếu bạn có thể. Đó là một mùi mã, không hiệu quả và có thể che giấu các vấn đề khác.
bfontaine

0

Câu trả lời của Jean-François Fabre set.intesection (* list_of_sets) chắc chắn là câu trả lời Pyhtonic nhất và đúng là câu trả lời được chấp nhận.

Đối với những người muốn sử dụng giảm, sau đây cũng sẽ hoạt động:

reduce(set.intersection, list_of_sets)

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.