Wie finde ich alle Teilmengen einer Menge mit genau n Elementen?

77

Ich schreibe ein Programm in Python und habe festgestellt, dass ein Problem, das ich lösen muss, es erfordert, dass ich bei einer Menge Svon nElementen (| S | = n) eine Funktion für alle möglichen Teilmengen einer bestimmten Reihenfolge m(dh mit m) teste Anzahl der Elemente). Verwenden Sie die Antwort, um eine Teillösung zu erstellen, und versuchen Sie es dann erneut mit der nächsten Reihenfolge m = m + 1, bis m = n.

Ich bin auf dem Weg, eine Lösung des Formulars zu schreiben:

def findsubsets(S, m):
    subsets = set([])
    ...
    return subsets

Da ich Python kannte, erwartete ich, dass es bereits eine Lösung geben würde.

Was ist der beste Weg, um dies zu erreichen?

Pietro Speroni
quelle
scipy.misc.comb(S, m)gibt die Anzahl der Teilmengen an, die Sie erhalten. Sie sollten eventuell eine Überprüfung durchführen, bevor Sie Ihren Code ausführen, da die Anzahl der m-großen Teilmengen von S sehr schnell sehr groß wird.
Martin Thoma
Hatte buchstäblich das gleiche Problem, machte mich daran, es selbst zu codieren und erkannte dann, dass es dafür eine Python-Bibliothek geben muss!
Srini

Antworten:

129

itertools.combinations ist dein Freund, wenn du Python 2.6 oder höher hast. Überprüfen Sie andernfalls den Link auf eine Implementierung einer äquivalenten Funktion.

import itertools
def findsubsets(S,m):
    return set(itertools.combinations(S, m))

S: Die Menge, für die Sie Teilmengen suchen möchten
m: Die Anzahl der Elemente in der Teilmenge

rekursiv
quelle
4
Ich würde keine Menge zurückgeben, sondern einfach den Iterator zurückgeben (oder einfach Kombinationen () verwenden, ohne eine Findsetsets () zu definieren ...)
@hop das OP fragte besonders nach Sets. Das Weglassen der Satzbesetzung ermöglicht Wiederholungen in verschiedenen Reihenfolgen, z. B.: (1,2,3), (2,3,1), (3,1,2) ...
James Bradbury
@ JamesBradbury: Es tut mir leid, ich verstehe nicht, was du meinst. Verwechseln Sie dies mit Permutationen?
62

Verwenden der kanonischen Funktion, um das Powerset von der Rezeptseite von itertools abzurufen :

from itertools import chain, combinations

def powerset(iterable):
    """
    powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)
    """
    xs = list(iterable)
    # note we return an iterator rather than a list
    return chain.from_iterable(combinations(xs,n) for n in range(len(xs)+1))

Verwendet wie:

>>> list(powerset("abc"))
[(), ('a',), ('b',), ('c',), ('a', 'b'), ('a', 'c'), ('b', 'c'), ('a', 'b', 'c')]

>>> list(powerset(set([1,2,3])))
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]

Wenn Sie möchten, können Sie Sets zuordnen, damit Sie Union, Kreuzung usw. verwenden können.

>>> map(set, powerset(set([1,2,3])))
[set([]), set([1]), set([2]), set([3]), set([1, 2]), set([1, 3]), set([2, 3]), set([1, 2, 3])]

>>> reduce(lambda x,y: x.union(y), map(set, powerset(set([1,2,3]))))
set([1, 2, 3])
brice
quelle
24

Hier ist eine Funktion, die Ihnen alle Teilmengen der ganzen Zahlen [0..n] gibt, nicht nur die Teilmengen einer bestimmten Länge:

from itertools import combinations, chain

def allsubsets(n):
    return list(chain(*[combinations(range(n), ni) for ni in range(n+1)]))

also zB

>>> allsubsets(3)
[(), (0,), (1,), (2,), (0, 1), (0, 2), (1, 2), (0, 1, 2)]
Alex
quelle
4
Nützliche Formel, aber verwenden Sie chain.from_iterableanstelle der Sternexpansion einen möglicherweise sehr langen Satz. Und was ist der Punkt , um die Kombinationen in eine Liste von Iterieren ( [ ... ]), Stern-Erweiterung Verkettungs in ein Iterator ( chain) und dann in eine Liste drehen wieder ? PS. Ein besseres Rezept finden Sie in der itertoolsDokumentation und in einer anderen (späteren) Antwort hier.
Alexis
Benannte Lambdas sind eine schlechte Praxis. Verwenden Sie defstattdessen ein.
Wjandrea
@wjandrea es ist ein Einzeiler.
Kowalski
@Kowalski was meinst du?
Wjandrea
@wjandrea Ich meine den Code vor Ihrer Bearbeitung.
Kowalski
5

Hier ist ein guter Weg mit leicht verständlichem Algorithmus.

import copy

nums = [2,3,4,5]
subsets = [[]]

for n in nums:
    prev = copy.deepcopy(subsets)
    [k.append(n) for k in subsets]
    subsets.extend(prev)

print(subsets) 
print(len(subsets))

# [[2, 3, 4, 5], [3, 4, 5], [2, 4, 5], [4, 5], [2, 3, 5], [3, 5], [2, 5], [5], 
# [2, 3, 4], [3, 4], [2, 4], [4], [2, 3], [3], [2], []]

# 16 (2^len(nums))

Darxtrix
quelle
1
Es sollte k.append(n)stattk.extend(n)
vereinheitlichen
4

Hier ist ein Pseudocode: Sie können dieselben rekursiven Aufrufe abschneiden, indem Sie die Werte für jeden Anruf speichern und vor der rekursiven Anrufprüfung prüfen, ob der Aufrufwert bereits vorhanden ist.

Der folgende Algorithmus enthält alle Teilmengen mit Ausnahme der leeren Menge.

list * subsets(string s, list * v) {

    if(s.length() == 1) {
        list.add(s);    
        return v;
    }
    else
    {
        list * temp = subsets(s[1 to length-1], v);
        int length = temp->size();

        for(int i=0;i<length;i++) {
            temp.add(s[0]+temp[i]);
        }

        list.add(s[0]);
        return temp;
    }
}

Wenn zum Beispiel s = "123" ist, lautet die Ausgabe:

1
2
3
12
13
23
123
Kofhearts
quelle
3

Ohne zu verwendenitertools :

In Python 3 können Sie yield fromder Buit-In- setKlasse eine Subset-Generator-Methode hinzufügen :

class SetWithSubset(set):
    def subsets(self):
        s1 = []
        s2 = list(self)

        def recfunc(i=0):            
            if i == len(s2):
                yield frozenset(s1)
            else:                
                yield from recfunc(i + 1)
                s1.append(s2[ i ])
                yield from recfunc(i + 1)
                s1.pop()

        yield from recfunc()

Zum Beispiel funktioniert das folgende Snippet wie erwartet:

x = SetWithSubset({1,2,3,5,6})
{2,3} in x.subsets()            # True
set() in x.subsets()            # True
x in x.subsets()                # True
x|{7} in x.subsets()            # False
set([5,3]) in x.subsets()       # True - better alternative: set([5,3]) < x
len(x.subsets())                # 32
Amin Azarbadegan
quelle
große Verwendung von yield fromund mit einer algorithmischen Erklärung
Ori
0

$ python -c "import itertools; a=[2,3,5,7,11]; print sum([list(itertools.combinations(a, i)) for i in range(len(a)+1)], [])" [(), (2,), (3,), (5,), (7,), (11,), (2, 3), (2, 5), (2, 7), (2, 11), (3, 5), (3, 7), (3, 11), (5, 7), (5, 11), (7, 11), (2, 3, 5), (2, 3, 7), (2, 3, 11), (2, 5, 7), (2, 5, 11), (2, 7, 11), (3, 5, 7), (3, 5, 11), (3, 7, 11), (5, 7, 11), (2, 3, 5, 7), (2, 3, 5, 11), (2, 3, 7, 11), (2, 5, 7, 11), (3, 5, 7, 11), (2, 3, 5, 7, 11)]

lidaobing
quelle
0
>>>Set = ["A", "B","C","D"]
>>>n = 2
>>>Subsets=[[i for i,s in zip(Set, status) if int(s)  ] for status in [(format(bit,'b').zfill(len(Set))) for bit in range(2**len(Set))] if sum(map(int,status)) == n]
>>>Subsets
[['C', 'D'], ['B', 'D'], ['B', 'C'], ['A', 'D'], ['A', 'C'], ['A', 'B']]
Mohamed Moawia
quelle
2
Erklären Sie, warum das tun würde, wonach gefragt wird.
Jwenting
Probieren
0

Eine andere Lösung mit Rekursion:

def subsets(nums: List[int]) -> List[List[int]]:
    n = len(nums)
    output = [[]]
    
    for num in nums:
        output += [curr + [num] for curr in output]
    
    return output

Ausgehend von einer leeren Teilmenge in der Ausgabeliste. Bei jedem Schritt berücksichtigen wir eine neue Ganzzahl und generieren aus den vorhandenen neue Teilmengen.

Melchia
quelle