Как получить все подмножества набора? (powerset)


103

Учитывая набор

{0, 1, 2, 3}

Как я могу произвести подмножества:

[set(),
 {0},
 {1},
 {2},
 {3},
 {0, 1},
 {0, 2},
 {0, 3},
 {1, 2},
 {1, 3},
 {2, 3},
 {0, 1, 2},
 {0, 1, 3},
 {0, 2, 3},
 {1, 2, 3},
 {0, 1, 2, 3}]

Ответы:


145

На itertoolsстранице Python есть точный powersetрецепт для этого:

from itertools import chain, combinations

def powerset(iterable):
    "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

Выход:

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

Если вам не нравится этот пустой кортеж в начале, вы можете просто изменить rangeоператор, range(1, len(s)+1)чтобы избежать комбинации нулевой длины.


1
Это самый быстрый ответ, который я смог найти, сравнив некоторые другие решения на этой странице с этим с использованием модуля Python timeit. Однако в некоторых случаях, если вам нужно изменить результирующий вывод (например, объединить буквы для формирования строк), написание пользовательского рецепта с использованием генераторов и создание желаемого вывода (например, сложение двух строк) может быть намного быстрее.
Ceasar Bautista

зачем s = list(iterable)нужен?
Джек Стивенс

@JackStevens, потому что итерируемые объекты не могут быть перемотаны и не требуются для __len__реализации; попробуйте powerset((n for n in range(3)))без переноса списка.
Hoefling

1
для больших строк это потребляет много памяти!
NoobEditor

1
@AlexandreHuat: Диапазоны - это ленивые последовательности, а не итераторы. powerset(range(3))будет нормально работать даже безs = list(iterable) .
user2357112 поддерживает Монику

50

Вот еще код для набора мощности. Это написано с нуля:

>>> def powerset(s):
...     x = len(s)
...     for i in range(1 << x):
...         print [s[j] for j in range(x) if (i & (1 << j))]
...
>>> powerset([4,5,6])
[]
[4]
[5]
[4, 5]
[6]
[4, 6]
[5, 6]
[4, 5, 6]

Комментарий Марка Рушакова применим здесь: «Если вам не нравится этот пустой кортеж в начале, on.», Вы можете просто изменить оператор диапазона на range (1, len (s) +1), чтобы избежать комбинации длины 0 ", за исключением моего случая, когда вы меняете for i in range(1 << x)на for i in range(1, 1 << x).


Возвращаясь к этому годы спустя, я бы теперь написал это так:

def powerset(s):
    x = len(s)
    masks = [1 << i for i in range(x)]
    for i in range(1 << x):
        yield [ss for mask, ss in zip(masks, s) if i & mask]

И тогда тестовый код будет выглядеть так, скажем:

print(list(powerset([4, 5, 6])))

Использование yieldозначает, что вам не нужно вычислять все результаты в одном фрагменте памяти. Предполагается, что предварительный расчет масок вне основного цикла является стоящей оптимизацией.


6
Это творческий ответ. Однако я измерил его с помощью timeit, чтобы сравнить с Марком Рушаковым, и заметил, что он был значительно медленнее. Чтобы сгенерировать набор мощности из 16 элементов 100 раз, мои измерения были 0,55 против 15,6.
Ceasar Bautista,

19

Если вы ищете быстрый ответ, я просто искал "python power set" в Google и нашел следующее: Python Power Set Generator

Вот копия кода с этой страницы:

def powerset(seq):
    """
    Returns all the subsets of this set. This is a generator.
    """
    if len(seq) <= 1:
        yield seq
        yield []
    else:
        for item in powerset(seq[1:]):
            yield [seq[0]]+item
            yield item

Это можно использовать так:

 l = [1, 2, 3, 4]
 r = [x for x in powerset(l)]

Теперь r - это список всех нужных вам элементов, который можно отсортировать и распечатать:

r.sort()
print r
[[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 4], [1, 3], [1, 3, 4], [1, 4], [2], [2, 3], [2, 3, 4], [2, 4], [3], [3, 4], [4]]

1
В случае пустой массив в качестве входных данных, приведенный выше код будет вернуться [[][]], чтобы исправить , что только отдельные случаи для проверки длиныif len(seq) == 0: yield [] elif len(seq) == 1: yield seq yield []
Ayush

3
Для справки, я измерил это (с редактированием Аюш) с помощью timeit и сравнил его с рецептом powerset в ответе Марка Рушакова. На моей машине для генерации набора мощности из 16 элементов 100 раз этот алгоритм занял 1,36 секунды, а алгоритм Рушакова - 0,55.
Ceasar Bautista

Какая будет временная сложность для этого?
CodeQuestor

13
def powerset(lst):
    return reduce(lambda result, x: result + [subset + [x] for subset in result],
                  lst, [[]])

8

Есть доработка powerset:

def powerset(seq):
    """
    Returns all the subsets of this set. This is a generator.
    """
    if len(seq) <= 0:
        yield []
    else:
        for item in powerset(seq[1:]):
            yield [seq[0]]+item
            yield item

8

TL; DR (перейти непосредственно к упрощению)

Я знаю, что ранее уже добавлял ответ, но мне очень нравится моя новая реализация. Я беру набор в качестве ввода, но на самом деле он может быть любым итеративным, и я возвращаю набор наборов, который является набором мощности ввода. Мне нравится этот подход, потому что он больше соответствует математическому определению набора мощности ( набора всех подмножеств ).

def power_set(A):
    """A is an iterable (list, tuple, set, str, etc)
    returns a set which is the power set of A."""
    length = len(A)
    l = [a for a in A]
    ps = set()

    for i in range(2 ** length):
        selector = f'{i:0{length}b}'
        subset = {l[j] for j, bit in enumerate(selector) if bit == '1'}
        ps.add(frozenset(subset))

    return ps

Если вам нужен именно тот результат, который вы опубликовали в своем ответе, используйте это:

>>> [set(s) for s in power_set({1, 2, 3, 4})]
[{3, 4},
 {2},
 {1, 4},
 {2, 3, 4},
 {2, 3},
 {1, 2, 4},
 {1, 2},
 {1, 2, 3},
 {3},
 {2, 4},
 {1},
 {1, 2, 3, 4},
 set(),
 {1, 3},
 {1, 3, 4},
 {4}]

Объяснение

Известно, что количество элементов силового набора такое 2 ** len(A), чтобы хорошо было видно в forконтуре.

Мне нужно преобразовать входные данные (в идеале набор) в список, потому что набор представляет собой структуру данных из уникальных неупорядоченных элементов, и порядок будет иметь решающее значение для создания подмножеств.

selectorявляется ключевым в этом алгоритме. Обратите внимание, что он selectorимеет ту же длину, что и входной набор, и для этого используется f-строка с отступом. По сути, это позволяет мне выбирать элементы, которые будут добавляться к каждому подмножеству во время каждой итерации. Скажем, входной набор состоит из 3 элементов {0, 1, 2}, поэтому селектор будет принимать значения от 0 до 7 (включительно), которые в двоичном формате:

000 # 0
001 # 1
010 # 2
011 # 3
100 # 4
101 # 5
110 # 6
111 # 7

Таким образом, каждый бит может служить индикатором того, нужно ли добавлять элемент исходного набора или нет. Посмотрите на двоичные числа и просто подумайте о каждом числе как об элементе супернабора, что 1означает, что jдолжен быть добавлен элемент по индексу , и 0означает, что этот элемент не следует добавлять.

Я использую понимание набора для создания подмножества на каждой итерации, и я конвертирую это подмножество в, frozensetчтобы добавить его вps (набор мощности). В противном случае я не смогу добавить его, потому что набор в Python состоит только из неизменяемых объектов.

Упрощение

Вы можете упростить код, используя некоторые понимания Python, чтобы избавиться от этих циклов for. Вы также можете использовать, zipчтобы избежать использования jиндекса, и код будет следующим:

def power_set(A):
    length = len(A)
    return {
        frozenset({e for e, b in zip(A, f'{i:{length}b}') if b == '1'})
        for i in range(2 ** length)
    }

Вот и все. Что мне нравится в этом алгоритме, так это то, что он более ясный и интуитивно понятный, чем другие, потому что он выглядит волшебно, на что можно положиться, itertoolsдаже если он работает так, как ожидалось.


5
def get_power_set(s):
  power_set=[[]]
  for elem in s:
    # iterate over the sub sets so far
    for sub_set in power_set:
      # add a new subset consisting of the subset at hand added elem
      power_set=power_set+[list(sub_set)+[elem]]
  return power_set

Например:

get_power_set([1,2,3])

Уступать

[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

1
Изменение переменной цикла ( power_set) в цикле, которым она управляет, - очень сомнительная практика. Например, предположим , что вы написали это вместо предложенной переменной модифицирующие кода: power_set += [list(sub_set)+[elem]]. Тогда цикл не прекращается.
hughdbrown

5

Я нашел следующий алгоритм очень понятным и простым:

def get_powerset(some_list):
    """Returns all subsets of size 0 - len(some_list) for some_list"""
    if len(some_list) == 0:
        return [[]]

    subsets = []
    first_element = some_list[0]
    remaining_list = some_list[1:]
    # Strategy: get all the subsets of remaining_list. For each
    # of those subsets, a full subset list will contain both
    # the original subset as well as a version of the subset
    # that contains first_element
    for partial_subset in get_powerset(remaining_list):
        subsets.append(partial_subset)
        subsets.append(partial_subset[:] + [first_element])

    return subsets

Другой способ создания набора мощности - создание всех двоичных чисел, имеющих nбиты. В качестве степенного набора количество nцифр равно 2 ^ n. Принцип этого алгоритма состоит в том, что элемент может присутствовать или не присутствовать в подмножестве, поскольку двоичная цифра может быть единицей или нулем, но не обоими сразу.

def power_set(items):
    N = len(items)
    # enumerate the 2 ** N possible combinations
    for i in range(2 ** N):
        combo = []
        for j in range(N):
            # test bit jth of integer i
            if (i >> j) % 2 == 1:
                combo.append(items[j])
        yield combo

Я нашел оба алгоритма, когда проходил MITx: 6.00.2x Introduction to Computational Thinking and Data Science, и считаю, что это один из самых простых для понимания алгоритмов, которые я видел.


3

Я просто хотел предложить наиболее понятное решение - версию против кодового гольфа.

from itertools import combinations

l = ["x", "y", "z", ]

def powerset(items):
    combo = []
    for r in range(len(items) + 1):
        #use a list to coerce a actual list from the combinations generator
        combo.append(list(combinations(items,r)))
    return combo

l_powerset = powerset(l)

for i, item in enumerate(l_powerset):
    print "All sets of length ", i
    print item

Результаты, достижения

Все наборы длины 0

[()]

Все наборы длины 1

[('x',), ('y',), ('z',)]

Все наборы длины 2

[('x', 'y'), ('x', 'z'), ('y', 'z')]

Все наборы длины 3

[('x', 'y', 'z')]

Для получения дополнительной информации см. Документацию itertools , а также статью в Википедии о наборах мощности


2

Просто быстрое освежение питания!

Набор мощности набора X - это просто набор всех подмножеств X, включая пустой набор

Пример набора X = (a, b, c)

Набор мощности = {{a, b, c}, {a, b}, {a, c}, {b, c}, {a}, {b}, {c}, {}}

Вот еще один способ найти набор мощности:

def power_set(input):
    # returns a list of all subsets of the list a
    if (len(input) == 0):
        return [[]]
    else:
        main_subset = [ ]
        for small_subset in power_set(input[1:]):
            main_subset += [small_subset]
            main_subset += [[input[0]] + small_subset]
        return main_subset

print(power_set([0,1,2,3]))

полный кредит на источник


2

Это можно сделать очень естественно с помощью itertools.product:

import itertools

def powerset(l):
    for sl in itertools.product(*[[[], [i]] for i in l]):
        yield {j for i in sl for j in i}

1
самый элегантный ответ на этот вопрос
Артур Б.

1

Простым способом было бы использовать внутреннее представление целых чисел в арифметике с дополнением до 2.

Двоичное представление целых чисел выглядит как {000, 001, 010, 011, 100, 101, 110, 111} для чисел в диапазоне от 0 до 7. Для целочисленного значения счетчика, считая 1 включением соответствующего элемента в коллекцию и '0' в качестве исключения мы можем создавать подмножества на основе последовательности подсчета. Числа должны быть сгенерированы от 0до, pow(2,n) -1где n - длина массива, то есть количество битов в двоичном представлении.

Простую функцию генератора подмножеств, основанную на ней, можно записать, как показано ниже. Это в основном полагается

def subsets(array):
    if not array:
        return
    else:
        length = len(array)
        for max_int in range(0x1 << length):
            subset = []
            for i in range(length):
                if max_int & (0x1 << i):
                    subset.append(array[i])
            yield subset

а затем его можно использовать как

def get_subsets(array):
    powerset = []
    for i in subsets(array):
        powerser.append(i)
    return powerset

Тестирование

Добавление следующего в локальный файл

if __name__ == '__main__':
    sample = ['b',  'd',  'f']

    for i in range(len(sample)):
        print "Subsets for " , sample[i:], " are ", get_subsets(sample[i:])

дает следующий вывод

Subsets for  ['b', 'd', 'f']  are  [[], ['b'], ['d'], ['b', 'd'], ['f'], ['b', 'f'], ['d', 'f'], ['b', 'd', 'f']]
Subsets for  ['d', 'f']  are  [[], ['d'], ['f'], ['d', 'f']]
Subsets for  ['f']  are  [[], ['f']]

Это может быть непрактичным с точки зрения ремонтопригодности или удобочитаемости, но это поразило меня. Спасибо, что поделились, умное решение!
Лори,

1

С пустым набором, который является частью всех подмножеств, вы можете использовать:

def subsets(iterable):
    for n in range(len(iterable) + 1):
        yield from combinations(iterable, n)

1

Почти во всех этих ответах используется listвместо set, что для меня показалось немного читерским. Итак, из любопытства я попытался сделать простую версию действительно наset и обобщить для других "новичков в Python".

Я обнаружил, что при работе с реализацией набора Python есть пара странностей . Основным сюрпризом для меня стало обращение с пустыми наборами. Это контрастирует с реализацией Ruby Set , где я могу просто сделать Set[Set[]]и получить Setодин пустой Set, поэтому сначала я нашел это немного запутанным.

Для обзора, работая powersetс sets, я столкнулся с двумя проблемами:

  1. set()принимает итерацию, поэтому set(set())вернется, set() потому что итерируемый пустой набор пуст (да, я думаю :))
  2. чтобы получить набор наборов, set({set()})и set.add(set)не будет работать, потому что set() не хешируется

Чтобы решить обе проблемы, я использовал frozenset(), что означает, что я не совсем получаю то, что хочу (буквально шрифт set), но использую общее setвзаимодействие.

def powerset(original_set):
  # below gives us a set with one empty set in it
  ps = set({frozenset()}) 
  for member in original_set:
    subset = set()
    for m in ps:
      # to be added into subset, needs to be
      # frozenset.union(set) so it's hashable
      subset.add(m.union(set([member]))
    ps = ps.union(subset)
  return ps

Ниже мы получаем frozensetправильные 2² (16) с на выходе:

In [1]: powerset(set([1,2,3,4]))
Out[2]:
{frozenset(),
 frozenset({3, 4}),
 frozenset({2}),
 frozenset({1, 4}),
 frozenset({3}),
 frozenset({2, 3}),
 frozenset({2, 3, 4}),
 frozenset({1, 2}),
 frozenset({2, 4}),
 frozenset({1}),
 frozenset({1, 2, 4}),
 frozenset({1, 3}),
 frozenset({1, 2, 3}),
 frozenset({4}),
 frozenset({1, 3, 4}),
 frozenset({1, 2, 3, 4})}

Поскольку в Python нет возможности иметь a setof sets, если вы хотите превратить эти frozensets в sets, вам придется отобразить их обратно в list( list(map(set, powerset(set([1,2,3,4])))) ) или изменить приведенное выше.


1

Возможно, вопрос устаревает, но я надеюсь, что мой код кому-то поможет.

def powSet(set):
    if len(set) == 0:
       return [[]]
    return addtoAll(set[0],powSet(set[1:])) + powSet(set[1:])

def addtoAll(e, set):
   for c in set:
       c.append(e)
   return set

фу, рекурсия! =)
étale-cohomology

Возможно, не самый эффективный способ, но всегда интересно увидеть рекурсивный способ!
Лисандро Ди Мео

1

Используйте функцию powerset()из пакета more_itertools.

Дает все возможные подмножества итерируемого

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

Если вам нужны наборы, используйте:

list(map(set, powerset(iterable)))

1
Так много людей изобретают велосипед здесь, ИМХО, это лучший ответ, поскольку он уже может быть в ваших зависимостях, поскольку он требуется многими распространенными библиотеками, например pytest. libraries.io/pypi/more-itertools/dependents
Lorey

1

Получение всех подмножеств с помощью рекурсии. Сумасшедший однострочный

from typing import List

def subsets(xs: list) -> List[list]:
    return subsets(xs[1:]) + [x + [xs[0]] for x in subsets(xs[1:])] if xs else [[]]

На основе решения Haskell

subsets :: [a] -> [[a]]
subsets [] = [[]]
subsets (x:xs) = map (x:) (subsets xs) ++ subsets xs

NameError: name 'List' is not defined
4LegsDrivenCat,

@ 4LegsDrivenCat Я добавил Listимпорт
Павел Рубин

1
def findsubsets(s, n): 
    return list(itertools.combinations(s, n)) 

def allsubsets(s) :
    a = []
    for x in range(1,len(s)+1):
        a.append(map(set,findsubsets(s,x)))      
    return a

Ответы, состоящие только из кода, считаются низкокачественными: обязательно объясните, что делает ваш код и как он решает проблему. Если вы добавите больше информации в свой пост, это поможет как спрашивающему, так и будущим читателям. См. « Объяснение ответов на основе кода»
Калос,

1

Сделать это можно так:

def powerset(x):
    m=[]
    if not x:
        m.append(x)
    else:
        A = x[0]
        B = x[1:]
        for z in powerset(B):
            m.append(z)
            r = [A] + z
            m.append(r)
    return m

print(powerset([1, 2, 3, 4]))

Выход:

[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3], [4], [1, 4], [2, 4], [1, 2, 4], [3, 4], [1, 3, 4], [2, 3, 4], [1, 2, 3, 4]]

1
Могу ли я посоветовать, публикуя код решения, достаточно любезно дать подробное объяснение того, что делает код и почему вы используете тот или иной метод для решения проблемы. Новым кодировщикам не следует просто смотреть на блок кода и копировать / вставлять его, не зная точно, что делает код и почему. Спасибо и добро пожаловать в Stackoverflow.
Йокай,

Действительно впечатляющий и рекурсивный ответ.
Иоанн

1

Я знаю это слишком поздно

Уже есть много других решений, но все же ...

def power_set(lst):
    pw_set = [[]]

    for i in range(0,len(lst)):
        for j in range(0,len(pw_set)):
            ele = pw_set[j].copy()
            ele = ele + [lst[i]]
            pw_set = pw_set + [ele]

    return pw_set

0

Это дико, потому что ни один из этих ответов на самом деле не возвращает фактический набор Python. Вот запутанная реализация, которая даст набор мощности, который на самом деле является Python set.

test_set = set(['yo', 'whatup', 'money'])
def powerset( base_set ):
    """ modified from pydoc's itertools recipe shown above"""
    from itertools import chain, combinations
    base_list = list( base_set )
    combo_list = [ combinations(base_list, r) for r in range(len(base_set)+1) ]

    powerset = set([])
    for ll in combo_list:
        list_of_frozensets = list( map( frozenset, map( list, ll ) ) ) 
        set_of_frozensets = set( list_of_frozensets )
        powerset = powerset.union( set_of_frozensets )

    return powerset

print powerset( test_set )
# >>> set([ frozenset(['money','whatup']), frozenset(['money','whatup','yo']), 
#        frozenset(['whatup']), frozenset(['whatup','yo']), frozenset(['yo']),
#        frozenset(['money','yo']), frozenset(['money']), frozenset([]) ])

Однако мне бы хотелось увидеть лучшую реализацию.


Хороший момент, но OP хочет, чтобы на выходе был список наборов, поэтому (в Python 3) вы можете сделать [*map(set, chain.from_iterable(combinations(s, r) for r in range(len(s)+1)))]; функция arg of mapможет быть, frozensetесли хотите.
PM 2Ring

0

Вот моя быстрая реализация с использованием комбинаций, но с использованием только встроенных модулей.

def powerSet(array):
    length = str(len(array))
    formatter = '{:0' + length + 'b}'
    combinations = []
    for i in xrange(2**int(length)):
        combinations.append(formatter.format(i))
    sets = set()
    currentSet = []
    for combo in combinations:
        for i,val in enumerate(combo):
            if val=='1':
                currentSet.append(array[i])
        sets.add(tuple(sorted(currentSet)))
        currentSet = []
    return sets

0

Все подмножества в диапазоне n установлены:

n = int(input())
l = [i for i in range (1, n + 1)]

for number in range(2 ** n) :
    binary = bin(number)[: 1 : -1]
    subset = [l[i] for i in range(len(binary)) if binary[i] == "1"]
    print(set(sorted(subset)) if number > 0 else "{}")

0
import math    
def printPowerSet(set,set_size): 
    pow_set_size =int(math.pow(2, set_size))
    for counter in range(pow_set_size):
    for j in range(set_size):  
        if((counter & (1 << j)) > 0):
            print(set[j], end = "")
    print("")
set = ['a', 'b', 'c']
printPowerSet(set,3)

0

Вариант вопроса - это упражнение, которое я вижу в книге «Открытие компьютерных наук: междисциплинарные проблемы, принципы и программирование на Python. Издание 2015 года». В этом упражнении 10.2.11 входом является просто целое число, а выходом должны быть наборы мощности. Вот мое рекурсивное решение (не использующее ничего, кроме базового python3)

def powerSetR(n):
    assert n >= 0
    if n == 0:
        return [[]]
    else:
        input_set = list(range(1, n+1)) # [1,2,...n]
        main_subset = [ ]
        for small_subset in powerSetR(n-1):
            main_subset += [small_subset]
            main_subset += [ [input_set[-1]] + small_subset]
        return main_subset

superset = powerSetR(4)
print(superset)       
print("Number of sublists:", len(superset))

И на выходе

[[], [4], [3], [4, 3], [2], [4, 2], [3, 2], [4, 3, 2], [1], [4, 1] » ], [3, 1], [4, 3, 1], [2, 1], [4, 2, 1], [3, 2, 1], [4, 3, 2, 1]] Количество подсписки: 16


0

Я не сталкивался с этой more_itertools.powersetфункцией и рекомендую ее использовать. Я также рекомендую не использовать порядок вывода по умолчанию itertools.combinations, часто вместо этого вы хотите минимизировать расстояние между позициями и отсортировать подмножества элементов с меньшим расстоянием между ними выше / перед элементами с большим расстоянием между ними.

На itertoolsстранице рецептов показано, что он используетchain.from_iterable

  • Обратите внимание, что rздесь соответствует стандартным обозначениям для нижней части биномиального коэффициента , sобычно упоминается как nв текстах по математике и на калькуляторах («n Выберите r»)
def powerset(iterable):
    "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

В других примерах здесь приводится набор мощности [1,2,3,4]таким образом, что 2-кортежи перечислены в «лексикографическом» порядке (когда мы печатаем числа как целые числа). Если я напишу расстояние между числами рядом с ним (то есть разницу), это покажет мою точку зрения:

121
132
143
231
242
341

Правильный порядок подмножеств должен быть порядком, который сначала «исчерпывает» минимальное расстояние, например:

121
231
341
132
242
143

Использование чисел здесь делает этот порядок ["a","b","c","d"]`` неправильным '', но рассмотрите, например, буквы, это более ясно, почему это может быть полезно для получения набора мощности в таком порядке:

ab ⇒ 1
bc ⇒ 1
cd ⇒ 1
ac ⇒ 2
bd ⇒ 2
ad ⇒ 3

Этот эффект более выражен с большим количеством элементов, и для моих целей он определяет разницу между осмысленным описанием диапазонов индексов набора мощности.

кодах Грея написано много и т. Д. О порядке вывода алгоритмов в комбинаторике, я не вижу в этом побочной проблемы).

На самом деле я просто написал довольно сложную программу, которая использует этот быстрый код целочисленного раздела для вывода значений в правильном порядке, но затем я обнаружил more_itertools.powersetи для большинства применений, вероятно, нормально просто использовать эту функцию следующим образом:

from more_itertools import powerset
from numpy import ediff1d

def ps_sorter(tup):
    l = len(tup)
    d = ediff1d(tup).tolist()
    return l, d

ps = powerset([1,2,3,4])

ps = sorted(ps, key=ps_sorter)

for x in ps:
    print(x)

()
(1,)
(2,)
(3,)
(4,)
(1, 2)
(2, 3)
(3, 4)
(1, 3)
(2, 4)
(1, 4)
(1, 2, 3)
(2, 3, 4)
(1, 2, 4)
(1, 3, 4)
(1, 2, 3, 4)

Я написал несколько более активных участие код , который будет печатать Powerset красиво (см репозитория для печати довольно функций я не включен здесь: print_partitions, print_partitions_by_length, и pprint_tuple).

Это все довольно просто, но все же может быть полезно, если вам нужен какой-то код, который позволит вам сразу получить доступ к различным уровням powerset:

from itertools import permutations as permute
from numpy import cumsum

# http://jeromekelleher.net/generating-integer-partitions.html
# via
# /programming/10035752/elegant-python-code-for-integer-partitioning#comment25080713_10036764

def asc_int_partitions(n):
    a = [0 for i in range(n + 1)]
    k = 1
    y = n - 1
    while k != 0:
        x = a[k - 1] + 1
        k -= 1
        while 2 * x <= y:
            a[k] = x
            y -= x
            k += 1
        l = k + 1
        while x <= y:
            a[k] = x
            a[l] = y
            yield tuple(a[:k + 2])
            x += 1
            y -= 1
        a[k] = x + y
        y = x + y - 1
        yield tuple(a[:k + 1])

# https://stackoverflow.com/a/6285330/2668831
def uniquely_permute(iterable, enforce_sort=False, r=None):
    previous = tuple()
    if enforce_sort: # potential waste of effort (default: False)
        iterable = sorted(iterable)
    for p in permute(iterable, r):
        if p > previous:
            previous = p
            yield p

def sum_min(p):
    return sum(p), min(p)

def partitions_by_length(max_n, sorting=True, permuting=False):
    partition_dict = {0: ()}
    for n in range(1,max_n+1):
        partition_dict.setdefault(n, [])
        partitions = list(asc_int_partitions(n))
        for p in partitions:
            if permuting:
                perms = uniquely_permute(p)
                for perm in perms:
                    partition_dict.get(len(p)).append(perm)
            else:
                partition_dict.get(len(p)).append(p)
    if not sorting:
        return partition_dict
    for k in partition_dict:
        partition_dict.update({k: sorted(partition_dict.get(k), key=sum_min)})
    return partition_dict

def print_partitions_by_length(max_n, sorting=True, permuting=True):
    partition_dict = partitions_by_length(max_n, sorting=sorting, permuting=permuting)
    for k in partition_dict:
        if k == 0:
            print(tuple(partition_dict.get(k)), end="")
        for p in partition_dict.get(k):
            print(pprint_tuple(p), end=" ")
        print()
    return

def generate_powerset(items, subset_handler=tuple, verbose=False):
    """
    Generate the powerset of an iterable `items`.

    Handling of the elements of the iterable is by whichever function is passed as
    `subset_handler`, which must be able to handle the `None` value for the
    empty set. The function `string_handler` will join the elements of the subset
    with the empty string (useful when `items` is an iterable of `str` variables).
    """
    ps = {0: [subset_handler()]}
    n = len(items)
    p_dict = partitions_by_length(n-1, sorting=True, permuting=True)
    for p_len, parts in p_dict.items():
        ps.setdefault(p_len, [])
        if p_len == 0:
            # singletons
            for offset in range(n):
                subset = subset_handler([items[offset]])
                if verbose:
                    if offset > 0:
                        print(end=" ")
                    if offset == n - 1:
                        print(subset, end="\n")
                    else:
                        print(subset, end=",")
                ps.get(p_len).append(subset)
        for pcount, partition in enumerate(parts):
            distance = sum(partition)
            indices = (cumsum(partition)).tolist()
            for offset in range(n - distance):
                subset = subset_handler([items[offset]] + [items[offset:][i] for i in indices])
                if verbose:
                    if offset > 0:
                        print(end=" ")
                    if offset == n - distance - 1:
                        print(subset, end="\n")
                    else:
                        print(subset, end=",")
                ps.get(p_len).append(subset)
        if verbose and p_len < n-1:
            print()
    return ps

В качестве примера я написал демонстрационную программу CLI, которая принимает строку в качестве аргумента командной строки:

python string_powerset.py abcdef

a, b, c, d, e, f

ab, bc, cd, de, ef
ac, bd, ce, df
ad, be, cf
ae, bf
af

abc, bcd, cde, def
abd, bce, cdf
acd, bde, cef
abe, bcf
ade, bef
ace, bdf
abf
aef
acf
adf

abcd, bcde, cdef
abce, bcdf
abde, bcef
acde, bdef
abcf
abef
adef
abdf
acdf
acef

abcde, bcdef
abcdf
abcef
abdef
acdef

abcdef

0

Если вам нужна определенная длина подмножеств, вы можете сделать это следующим образом:

from itertools import combinations
someSet = {0, 1, 2, 3}
([x for i in range(len(someSet)+1) for x in combinations(someSet,i)])

В более общем случае для подмножеств произвольной длины вы можете изменять диапазон диапазонов. На выходе

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


-1
def powerset(some_set):
    res = [(a,b) for a in some_set for b in some_set]
    return res

6
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно того, почему и / или как этот код отвечает на вопрос, улучшает его долгосрочную ценность. Прочтите « Как ответить» и отредактируйте свой ответ, чтобы улучшить его.
blurfus

2
Использование @blurfus всегда является хорошей практикой, но это особенно важно, когда вы отвечаете на вопрос десятилетней давности с помощью 28 других ответов. Почему это улучшение по сравнению с принятым ответом? Что дает этот ответ, чего не предлагают другие ответы?
Джереми Кейни,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.