383

I have a list of sets:

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

I want s1 ∩ s2 ∩ s3 ...

I can write a function to do it by performing a series of pairwise s1.intersection(s2), etc.

Is there a recommended, better, or built-in way?

martineau
  • 119,623
  • 25
  • 170
  • 301
user116293
  • 5,534
  • 4
  • 25
  • 17

7 Answers7

632

From Python version 2.6 on you can use multiple arguments to set.intersection(), like

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

If the sets are in a list, this translates to:

u = set.intersection(*setlist)

where *a_list is list expansion

Note that set.intersection is not a static method, but this uses the functional notation to apply intersection of the first set with the rest of the list. So if the argument list is empty this will fail.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
sth
  • 222,467
  • 53
  • 283
  • 367
  • 1
    So what to do when there are possibly zero arguments? In one line? – Radio Controlled Jul 08 '20 at 13:18
  • 1
    @RadioControlled For a one liner that works when setlist is empty, use `u = set.intersection(*setlist) if setlist else set()` – proteome Jul 13 '20 at 06:24
  • any comment on complexity of the sol. given above ? – CKM Sep 27 '20 at 04:26
  • @CKM, exactly, do we need to order the sets in `setlist` beforehand by size or does the function do this for us? This would be contradicted by the statement "apply intersection of the first set with the rest of the list". – Radio Controlled Oct 22 '20 at 14:01
  • 1
    @RadioControlled The intersection of no sets is not mathematically defined, so this _should_ fail. See Patrick Suppes' "Axiomatic Set Theory" for a reference. – dionyziz Dec 06 '20 at 05:46
  • https://math.stackexchange.com/questions/483002/what-does-the-big-intersection-or-union-sign-of-a-set-mean – Radio Controlled Dec 07 '20 at 10:30
  • Please explain your use of the non-static `set.intersection()` in more detail — I don't understand how you are able to use it this way. – martineau Dec 07 '20 at 17:49
94

As of 2.6, set.intersection takes arbitrarily many iterables.

>>> 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])
Mike Graham
  • 73,987
  • 14
  • 101
  • 130
31

Clearly set.intersection is what you want here, but in case you ever need a generalisation of "take the sum of all these", "take the product of all these", "take the xor of all these", what you are looking for is the reduce function:

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

or

print(reduce((lambda x,y: x&y), [{1,2,3},{2,3,4},{3,4,5}])) # = {3}
Thomas Ahle
  • 30,774
  • 21
  • 92
  • 114
  • Here, I would be quite certain the order of list matters for speed. Order by increasing size -- or decreasing expected intersection size of neighboring sets in the list, to be more accurate. – Radio Controlled Oct 22 '20 at 14:06
12

If you don't have Python 2.6 or higher, the alternative is to write an explicit for loop:

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

You can also use reduce:

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

However, many Python programmers dislike it, including Guido himself:

About 12 years ago, Python aquired lambda, reduce(), filter() and map(), courtesy of (I believe) a Lisp hacker who missed them and submitted working patches. But, despite of the PR value, I think these features should be cut from Python 3000.

So now reduce(). This is actually the one I've always hated most, because, apart from a few examples involving + or *, almost every time I see a reduce() call with a non-trivial function argument, I need to grab pen and paper to diagram what's actually being fed into that function before I understand what the reduce() is supposed to do. So in my mind, the applicability of reduce() is pretty much limited to associative operators, and in all other cases it's better to write out the accumulation loop explicitly.

Ayman Hourieh
  • 132,184
  • 23
  • 144
  • 116
  • 9
    Note that Guido says using `reduce` is "limited to associative operators", which is applicable in this case. `reduce` is very often hard to figure out, but for `&` isn't so bad. – Mike Graham Mar 29 '10 at 23:21
  • [`set_list and reduce(set.intersection, set_list)`](http://stackoverflow.com/a/1404146/4279) – jfs Dec 05 '12 at 06:51
  • Check out https://www.python.org/doc/essays/list2str/ for useful optimizations involving reduce. It can in general be used quite nicely to build lists, sets, strings etc. Worth a look also is https://github.com/EntilZha/PyFunctional – Andreas Nov 16 '16 at 06:03
  • Note you could optimize by breaking off your loop when `result` is empty. – bfontaine Aug 25 '17 at 13:52
2

I believe the simplest thing to do is:

#assuming three sets
set1 = {1,2,3,4,5}
set2 = {2,3,8,9}
set3 = {2,10,11,12}

#intersection
set4 = set1 & set2 & set3

set4 will be the intersection of set1 , set2, set3 and will contain the value 2.

print(set4)

set([2])
Stamatis Tiniakos
  • 698
  • 1
  • 11
  • 33
  • OP is asking to apply intersection to a list. The effort to spell out each element of the list with an & operator is futile at best. – 0x5050 Feb 01 '22 at 06:49
1

Here I'm offering a generic function for multiple set intersection trying to take advantage of the best method available:

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 might dislike reduce, but I'm kind of fond of it :)

tzot
  • 92,761
  • 29
  • 141
  • 204
  • You should check the length of `sets` instead of trying to access `sets[0]` and catching the `IndexError`. – bfontaine Aug 25 '17 at 13:51
  • This isn't a plain check; `a_set` is used at the final return. – tzot Aug 26 '17 at 17:53
  • Can’t you do `return reduce(sets[0], sets[1:]) if sets else set()`? – bfontaine Aug 28 '17 at 08:05
  • Ha yes, thank you. The code should change because relying on a `try`/`except` should be avoided if you can. It’s a code smell, is inefficient, and can hide other problems. – bfontaine Aug 30 '17 at 09:24
1

Jean-François Fabre set.intesection(*list_of_sets) answer is definetly the most Pyhtonic and is rightly the accepted answer.

For those that want to use reduce, the following will also work:

reduce(set.intersection, list_of_sets)

Minas
  • 19
  • 1