Der beste Weg, um den Schnittpunkt mehrerer Mengen zu finden?


265

Ich habe eine Liste von Sets:

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

Ich möchte s1 ∩ s2 ∩ s3 ...

Ich kann eine Funktion schreiben, um dies zu tun, indem ich eine Reihe von paarweisen s1.intersection(s2)usw. ausführe .

Gibt es eine empfohlene, bessere oder integrierte Methode?

Antworten:


453

Ab Python Version 2.6 können Sie mehrere Argumente verwenden set.intersection(), um

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

Wenn sich die Sets in einer Liste befinden, bedeutet dies:

u = set.intersection(*setlist)

Wo *a_listist die Listenerweiterung?

Beachten Sie, dass set.intersectionist nicht eine statische Methode, aber dies verwendet die funktionale Notation Schnittpunkt des ersten Satzes anzuwenden mit dem Rest der Liste. Wenn die Argumentliste leer ist, schlägt dies fehl.


64

Ab 2.6 werden set.intersectionbeliebig viele Iterables benötigt.

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

Klar set.intersectionist, was Sie hier wollen, aber falls Sie jemals eine Verallgemeinerung von "nimm die Summe all dieser", "nimm das Produkt von all diesen", "nimm das xor von all diesen" brauchen, was du suchst, ist das reduceFunktion:

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

oder

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

12

Wenn Sie nicht über Python 2.6 oder höher verfügen, können Sie alternativ eine explizite for-Schleife schreiben:

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

Sie können auch verwenden reduce:

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

Viele Python-Programmierer mögen es jedoch nicht, einschließlich Guido selbst :

Vor ungefähr 12 Jahren erwarb Python Lambda, Reduce (), Filter () und Map (), mit freundlicher Genehmigung eines (ich glaube) Lisp-Hackers, der sie verpasst und funktionierende Patches eingereicht hat. Trotz des PR-Werts sollten diese Funktionen aus Python 3000 entfernt werden.

Reduzieren Sie jetzt (). Dies ist eigentlich die, die ich immer am meisten gehasst habe, denn abgesehen von einigen Beispielen mit + oder * muss ich fast jedes Mal, wenn ich einen redu () - Aufruf mit einem nicht trivialen Funktionsargument sehe, Stift und Papier greifen Diagramm, was tatsächlich in diese Funktion eingespeist wird, bevor ich verstehe, was das redu () tun soll. Meiner Meinung nach ist die Anwendbarkeit von redu () weitgehend auf assoziative Operatoren beschränkt, und in allen anderen Fällen ist es besser, die Akkumulationsschleife explizit zu schreiben.


8
Beachten Sie, dass Guido sagt, die Verwendung reducesei "auf assoziative Operatoren beschränkt", was in diesem Fall anwendbar ist. reduceist sehr oft schwer herauszufinden, aber für &ist nicht so schlimm.
Mike Graham


Unter python.org/doc/essays/list2str finden Sie nützliche Optimierungen zum Reduzieren. Es kann im Allgemeinen sehr gut verwendet werden, um Listen, Sets, Strings usw. zu erstellen. Einen Blick wert ist auch github.com/EntilZha/PyFunctional
Andreas

Beachten Sie, dass Sie optimieren können, indem Sie die Schleife abbrechen, wenn sie resultleer ist.
Bfontaine

1

Hier biete ich eine generische Funktion für Kreuzungen mit mehreren Mengen an, um die beste verfügbare Methode zu nutzen:

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 mag es vielleicht nicht reduce, aber ich mag es irgendwie :)


Sie sollten die Länge von überprüfen, setsanstatt zu versuchen, auf die zuzugreifen sets[0]und sie zu fangen IndexError.
Bfontaine

Dies ist kein einfacher Scheck; a_setwird bei der endgültigen Rückgabe verwendet.
Zot

Kannst du nicht tun return reduce(sets[0], sets[1:]) if sets else set()?
Bfontaine

Ha ja, danke. Der Code sollte sich ändern, da das Verlassen auf ein try/ exceptvermieden werden sollte, wenn Sie können. Es ist ein Code-Geruch, ist ineffizient und kann andere Probleme verbergen.
Bfontaine

0

Die Antwort von Jean-François Fabre set.intesection (* list_of_sets) ist definitiv die pyhtonischste und zu Recht die akzeptierte Antwort.

Für diejenigen, die Reduce verwenden möchten, funktioniert auch Folgendes:

reduce(set.intersection, list_of_sets)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.