Як я можу знайти всі підмножини набору з точно n елементами?


77

Я пишу програму на Python, і я зрозумів, що проблема, яку мені потрібно вирішити, вимагає від мене набору Sз nелементами (| S | = n) тестувати функцію на всіх можливих підмножинах певного порядку m(тобто з m кількість елементів). Щоб використати відповідь для часткового розв’язання, а потім повторіть спробу з наступним порядком m = m + 1, поки m = n.

Я збираюся написати рішення у формі:

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

Але знаючи Python, я очікував, що рішення вже буде там.

Який найкращий спосіб цього досягти?


scipy.misc.comb(S, m)надає кількість підмножин, які ви отримаєте. Зрештою, вам слід зробити перевірку, перш ніж виконувати свій код, оскільки кількість підмножин розміром m дуже швидко стає дуже великим.
Мартін Тома

Буквально мала таку ж проблему, вирішила її кодувати сама, а потім зрозуміла, що для цього повинна існувати бібліотека Python!
Сріні

Відповіді:


130

itertools.combinations - ваш друг, якщо у вас Python 2.6 або новіша . В іншому випадку перевірте посилання на наявність еквівалентної функції.

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

S: Набір, для якого потрібно знайти підмножини
m: Кількість елементів у підмножині


4
я б не повернув набір, а просто повернув ітератор (або просто використовував комбінації () без визначення findubsets () ...)

@hop ОП особливо просив набори. Опускання встановленого складу дозволяє повторення в різних порядках, наприклад: (1,2,3), (2,3,1), (3,1,2) ...
Джеймс Бредбері

@JamesBradbury: Вибачте, я не розумію, що ви маєте на увазі. Ви плутаєте це з перестановками?

63

Використання канонічної функції для отримання набору потужності зі сторінки рецептів itertools :

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

Використовується як:

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

map для наборів, якщо ви хочете, щоб ви могли використовувати об'єднання, перетин тощо ...:

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

25

Ось функція, яка надає вам усі підмножини цілих чисел [0..n], а не лише підмножини заданої довжини:

from itertools import combinations, chain

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

так наприклад

>>> allsubsets(3)
[(), (0,), (1,), (2,), (0, 1), (0, 2), (1, 2), (0, 1, 2)]

4
Корисна формула, але використовуйте chain.from_iterableзамість розширення зірок потенційно дуже довгий набір. І що точка переборі комбінацій в список ( [ ... ]), зірка розширюється, ланцюжки в итератор ( chain), а потім перетворюється в список ще раз ? PS. кращий рецепт - у itertoolsдокументації, а в іншій (пізніше) відповіді тут.
alexis

Названі лямбди - погана практика. Використовуйте defзамість цього.
wjandrea

@wjandrea це однокласний.
Ekrem Dinçel

@Kowalski що ти маєш на увазі?
wjandrea

@Kowalski Так, звичайно, це був один лайнер. Що ви маєте на увазі?
wjandrea

5

Ось один акуратний спосіб з простим для розуміння алгоритмом.

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


1
Це має бути k.append(n)замістьk.extend(n)
об’єднати

4

Ось деякий псевдокод - ви можете вирізати ті самі рекурсивні виклики, зберігаючи значення кожного виклику, як ви рухаєтесь, і перед рекурсивним викликом перевіряючи, чи вже є значення виклику.

Наступний алгоритм матиме всі підмножини, крім порожнього набору.

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

Так, наприклад, якщо s = "123", тоді виводиться:

1
2
3
12
13
23
123

3

Без використанняitertools :

У Python 3 ви можете використовувати yield fromдля додавання методу генератора підмножин до вбудованого setкласу:

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

Наприклад, нижче фрагмент працює належним чином:

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

велике використання yield fromта з алгоритмічним поясненням
Орі

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


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

2
поясни, чому це зробить те, про що просять.
jwenting

Просто спробуйте, і ви побачите, чи зробить те, про що просив
Мохамед Моавія,

0

Інше рішення з використанням рекурсії:

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

Починаючи з порожньої підмножини у вихідному списку. На кожному кроці ми беремо до уваги нове ціле число та генеруємо нові підмножини з існуючих.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.