Як отримати всі підмножини набору? (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)щоб уникнути комбінації довжини 0.


1
Це найшвидша відповідь, яку я міг знайти, порівнявши деякі інші рішення на цій сторінці з цією за допомогою модуля timeit Python. Однак у певних випадках, якщо вам потрібно змінити отриманий результат (наприклад, об'єднати літери, щоб утворити рядки), написання власного рецепту з використанням генераторів та створення необхідного результату (наприклад, складання двох рядків) може бути набагато швидшим.
Цезар Баутіста,

навіщо 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

Ось додатковий код для powerset. Це написано з нуля:

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

Тут застосовний коментар Марка Рушакова: "Якщо вам не подобається той пустий кортеж на початку, увімкнено", ви можете просто змінити оператор діапазону на діапазон (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.
Цезар Баутіста

19

Якщо ви шукаєте швидку відповідь, я просто шукав у Google "python power set" і придумав наступне: Python Power Set Generator

Ось copy-paste з коду на цій сторінці:

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 []
Аюш

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

Якою буде для цього складність часу?
CodeQuestor


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, щоб ви могли позбутися цих для циклів. Ви також можете використовувати, 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 Вступ до обчислювального мислення та науки про дані, і вважаю, що це один із найпростіших алгоритмів для розуміння, який я бачив.


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

З порожнім набором, який є частиною всіх підмножин, ви можете використовувати:

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

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

Майже всі ці відповіді використовують listскоріше ніж set, що для мене здавалося трохи обманом. Отже, з цікавості я спробував зробити справжню просту версію setі підвести підсумки для інших "нових для Python" людей.

Я виявив, що є кілька дивацтв у роботі з реалізацією набору Python . Головним сюрпризом для мене стало поводження з порожніми наборами. Це на відміну від реалізації набору Ruby , де я можу просто зробити Set[Set[]]і отримати Setпусту Set, що містить , тому мені спочатку це здалося трохи заплутаним.

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

  1. set()приймає iterable, тому set(set())повернеться, set() тому що порожній набір iterable порожній (duh, я думаю :))
  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

Нижче ми отримуємо 2² (16) frozensetс як результат:

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

Оскільки немає ніякого способу , щоб мати setз setз в Python, якщо ви хочете , щоб перетворити ці frozensetсек в setсек, ви повинні зіставити їх назад в 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-

Мабуть, не найефективніший спосіб, але завжди цікаво побачити рекурсивний шлях!
Лісандро Ді Мео,

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
Тут так багато людей винаходять колесо, IMHO - це найкраща відповідь, оскільки це, можливо, вже є у ваших залежностях, оскільки це вимагається багатьма загальними бібліотеками, наприклад pytest. libraries.io/pypi/more-itertools/dependents
Лорі

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

Чи можу я запропонувати, коли публікуєте рішення коду, будьте люб'язними, щоб дати детальне пояснення того, що робить код, і чому ви використовуєте той чи інший метод для вирішення проблеми. Нові кодери не повинні просто розглядати блок коду та копіювати / вставляти його, не знаючи точно, що робить код і чому. Дякуємо та ласкаво просимо до 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 mapможе бути, frozensetякщо вам більше подобається.
PM 2, кільце,

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


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

6
Хоча цей код може відповісти на питання, надаючи додатковий контекст щодо того, чому та / або як цей код відповідає на питання, покращує його довгострокове значення. Подумайте про прочитання « Як відповісти» та відредагуйте свою відповідь, щоб покращити її.
розмитість

2
Що @blurfus - це завжди хороша практика, але особливо важливо, коли ви відповідаєте на десятилітнє запитання 28 іншими відповідями. Чому це покращення прийнятої відповіді? Чому ця відповідь сприяє, а інші відповіді не пропонують?
Джеремі Кейні,

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