¿Cómo obtener todos los subconjuntos de un conjunto? (set de poder)

103

Dado un conjunto

{0, 1, 2, 3}

¿Cómo puedo producir los subconjuntos?

[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}]
Patricio
fuente

Respuestas:

145

La itertoolspágina de Python tiene exactamente una powersetreceta para esto:

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

Salida:

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

Si no le gusta esa tupla vacía al principio, puede cambiar la rangedeclaración a range(1, len(s)+1)para evitar una combinación de longitud 0.

Mark Rushakoff
fuente
1
Esta es la respuesta más rápida que pude encontrar, comparando algunas otras soluciones en esta página con esta usando el módulo timeit de Python. Sin embargo, en ciertos casos, si necesita modificar la salida resultante (por ejemplo, uniendo las letras para formar cadenas), escribir una receta personalizada utilizando generadores y crear la salida que desee (por ejemplo, sumando dos cadenas) puede ser mucho más rápido.
Ceasar Bautista
¿Por qué se s = list(iterable)necesita?
Jack Stevens
@JackStevens porque los iterables no se pueden rebobinar y no es necesario que se hayan __len__implementado; Pruébelo powerset((n for n in range(3)))sin envolver la lista.
hoefling
1
para cadenas grandes, ¡esto consumiría mucha memoria!
NoobEditor
1
@AlexandreHuat: los rangos son secuencias perezosas, no iteradores. powerset(range(3))funcionaría bien incluso sins = list(iterable) .
user2357112 apoya a Monica el
50

Aquí hay más código para un powerset. Esto está escrito desde cero:

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

El comentario de Mark Rushakoff es aplicable aquí: "Si no te gusta esa tupla vacía al principio, encendido". Puedes cambiar la declaración de rango a rango (1, len (s) +1) para evitar una combinación de longitud 0 ", excepto que en mi caso cambia for i in range(1 << x)a for i in range(1, 1 << x).


Volviendo a esto años más tarde, ahora lo escribiría así:

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]

Y luego el código de prueba se vería así, di:

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

Usar yieldsignifica que no es necesario calcular todos los resultados en una sola pieza de memoria. Se supone que el cálculo previo de las máscaras fuera del bucle principal es una optimización útil.

hughdbrown
fuente
6
Esta es una respuesta creativa. Sin embargo, lo medí usando timeit para compararlo con Mark Rushakoff y noté que era significativamente más lento. Para generar el conjunto de potencia de 16 elementos 100 veces, mis medidas fueron 0,55 frente a 15,6.
Ceasar Bautista
19

Si está buscando una respuesta rápida, acabo de buscar "Python Power Set" en Google y se me ocurrió esto: Python Power Set Generator

Aquí hay una copia y pegado del código en esa página:

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

Esto se puede usar así:

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

Ahora r es una lista de todos los elementos que deseaba, y se puede ordenar e imprimir:

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]]
Edan Maor
fuente
1
En caso de una matriz vacía como entrada, el código anterior regresaría [[][]], para solucionarlo, solo separe los casos para la verificación de longitudif len(seq) == 0: yield [] elif len(seq) == 1: yield seq yield []
Ayush
3
Como referencia, medí esto (con la edición de Ayush) usando timeit y lo comparé con la receta de powerset en la respuesta de Mark Rushakoff. En mi máquina, para generar el conjunto de potencia de 16 elementos 100 veces, este algoritmo tomó 1,36 segundos mientras que el de Rushakoff tomó 0,55.
Ceasar Bautista
¿Cuál será la complejidad del tiempo para esto?
CodeQuestor
13
def powerset(lst):
    return reduce(lambda result, x: result + [subset + [x] for subset in result],
                  lst, [[]])
newacct
fuente
8

Hay un refinamiento de 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
Jingguo Yao
fuente
8

TL; DR (vaya directamente a Simplificación)

Sé que he agregado una respuesta anteriormente, pero realmente me gusta mi nueva implementación. Estoy tomando un conjunto como entrada, pero en realidad podría ser cualquier iterable, y estoy devolviendo un conjunto de conjuntos que es el conjunto de potencia de la entrada. Me gusta este enfoque porque está más alineado con la definición matemática de conjunto de potencia ( conjunto de todos los subconjuntos ).

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

Si desea exactamente el resultado que publicó en su respuesta, use esto:

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

Explicación

Se sabe que el número de elementos del conjunto de potencia es 2 ** len(A), por lo que se puede ver claramente en el forbucle.

Necesito convertir la entrada (idealmente un conjunto) en una lista porque por conjunto hay una estructura de datos de elementos únicos desordenados, y el orden será crucial para generar los subconjuntos.

selectores clave en este algoritmo. Tenga en cuenta que selectortiene la misma longitud que el conjunto de entrada, y para que esto sea posible, está usando una cadena f con relleno. Básicamente, esto me permite seleccionar los elementos que se agregarán a cada subconjunto durante cada iteración. Digamos que el conjunto de entrada tiene 3 elementos{0, 1, 2} , por lo que el selector tomará valores entre 0 y 7 (inclusive), que en binario son:

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

Por lo tanto, cada bit podría servir como indicador si se debe agregar o no un elemento del conjunto original. Mire los números binarios y piense en cada número como un elemento del superconjunto, lo que 1significa que un elemento en el índicej debe agregar y 0que este elemento no se debe agregar.

Estoy usando una comprensión de conjunto para generar un subconjunto en cada iteración, y convierto este subconjunto en un frozenset para poder agregarlo aps (conjunto de potencia). De lo contrario, no podré agregarlo porque un conjunto en Python consiste solo en objetos inmutables.

Simplificación

Puede simplificar el código usando algunas comprensiones de Python, para que pueda deshacerse de esos bucles for. También puede usar zippara evitar el uso de jindex y el código terminará como el siguiente:

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

Eso es. Lo que me gusta de este algoritmo es que es más claro e intuitivo que otros porque parece bastante mágico para confiar en itertoolsél, aunque funciona como se esperaba.

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

Por ejemplo:

get_power_set([1,2,3])

rendimiento

[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
jmkg
fuente
1
Modificar una variable de ciclo ( power_set) en el ciclo que gobierna es una práctica muy cuestionable. Por ejemplo, supongamos que usted escribió esto en vez de al código variable modificadora propuesta: power_set += [list(sub_set)+[elem]]. Entonces el bucle no termina.
hughdbrown
5

He encontrado el siguiente algoritmo muy claro y simple:

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

Otra forma de generar el conjunto de potencias es generando todos los números binarios que tienen nbits. Como conjunto de potencia, la cantidad de números con ndígitos es 2 ^ n. El principio de este algoritmo es que un elemento podría estar presente o no en un subconjunto, ya que un dígito binario podría ser uno o cero, pero no ambos.

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

Encontré ambos algoritmos cuando estaba tomando MITx: 6.00.2x Introducción al pensamiento computacional y la ciencia de datos, y considero que es uno de los algoritmos más fáciles de entender que he visto.

lmiguelvargasf
fuente
3

Solo quería ofrecer la solución más comprensible, la versión anti-código-golf.

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

Los resultados

Todos los conjuntos de longitud 0

[()]

Todos los conjuntos de longitud 1

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

Todos los conjuntos de longitud 2

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

Todos los conjuntos de longitud 3

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

Para obtener más información, consulte los documentos de itertools , también la entrada de wikipedia sobre conjuntos de potencia

Gourneau
fuente
2

¡Solo un repaso rápido del conjunto de energía!

El conjunto de potencia de un conjunto X, es simplemente el conjunto de todos los subconjuntos de X, incluido el conjunto vacío

Conjunto de ejemplo X = (a, b, c)

Conjunto de poder = {{a, b, c}, {a, b}, {a, c}, {b, c}, {a}, {b}, {c}, {}}

Aquí hay otra forma de encontrar el conjunto de potencia:

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

crédito completo a la fuente

grepit
fuente
2

Esto se puede hacer de forma muy natural con 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}
Paul Crowley
fuente
1
respuesta más elegante dada a esta pregunta
Arthur B.
1

Una forma sencilla sería aprovechar la representación interna de números enteros bajo aritmética de complemento a 2.

La representación binaria de números enteros es {000, 001, 010, 011, 100, 101, 110, 111} para números que van del 0 al 7. Para un valor de contador de números enteros, se considera 1 como inclusión del elemento correspondiente en la colección y '0' como exclusión podemos generar subconjuntos basados ​​en la secuencia de conteo. Los números tienen que ser generado a partir 0de pow(2,n) -1la que N es la longitud de la matriz es decir, número de bits en la representación binaria.

Una función de generador de subconjuntos simple basada en ella se puede escribir como se muestra a continuación. Básicamente se basa

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

y luego se puede usar como

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

Pruebas

Añadiendo lo siguiente en el archivo local

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

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

da la siguiente salida

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']]
ViFI
fuente
Esto puede no ser práctico en cuanto a facilidad de mantenimiento o legibilidad, pero me dejó alucinado. ¡Gracias por compartir, solución inteligente!
lorey
1

Con un conjunto vacío, que es parte de todos los subconjuntos, puede usar:

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

Casi todas estas respuestas usan en listlugar de set, lo que me pareció un poco engañoso. Entonces, por curiosidad, intenté hacer una versión simple realmente enset y resumida para otras personas "nuevas en Python".

Descubrí que hay un par de rarezas al tratar con la implementación de conjuntos de Python . La principal sorpresa para mí fue manejar sets vacíos. Esto contrasta con la implementación de Ruby's Set , donde simplemente puedo hacer Set[Set[]]y obtener Setuno que contenga uno vacío Set, por lo que inicialmente lo encontré un poco confuso.

Para revisar, al hacer powersetcon sets, encontré dos problemas:

  1. set()toma un iterable, por set(set())lo que volverá set() porque el conjunto vacío iterable está vacío (duh, supongo :))
  2. para obtener un conjunto de conjuntos, set({set()})y set.add(set)no funcionará porque set() no se puede mezclar

Para resolver ambos problemas, utilicé frozenset() utilicé , lo que significa que no obtengo lo que quiero (el tipo es literalmente set), pero hace uso de la setinteracción general .

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

A continuación, obtenemos 2² (16) frozensets correctamente como salida:

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

Como no hay forma de tener una setde sets en Python, si desea convertir estas frozensets en sets, tendrá que volver a mapearlas en a list( list(map(set, powerset(set([1,2,3,4])))) ) o modificar lo anterior.

Alex Moore-Niemi
fuente
1

Quizás la pregunta esté envejeciendo, pero espero que mi código ayude a alguien.

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
Lisandro Di Meo
fuente
ew, recursividad! =)
étale-cohomology
Probablemente no sea la forma más eficiente, ¡pero siempre es interesante ver la forma recursiva!
Lisandro Di Meo
1

Utilice la función powerset()del paquete more_itertools.

Produce todos los subconjuntos posibles del iterable

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

Si quieres conjuntos, usa:

list(map(set, powerset(iterable)))
Alexandre Huat
fuente
1
Tanta gente reinventando la rueda aquí, en mi humilde opinión, esta es la mejor respuesta, ya que puede que ya esté en sus dependencias, ya que muchas bibliotecas comunes la requieren, por ejemplo, pytest. libraries.io/pypi/more-itertools/dependents
lorey
1

Obteniendo todos los subconjuntos con recursividad. Una sola línea de culo loco

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

Basado en una solución de Haskell

subsets :: [a] -> [[a]]
subsets [] = [[]]
subsets (x:xs) = map (x:) (subsets xs) ++ subsets xs
Paweł Rubin
fuente
NameError: name 'List' is not defined
4LegsDrivenCat
@ 4LegsDrivenCat He agregado Listimportación
Paweł Rubin
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
Mohamed_Abdelmadjid Boudis
fuente
Las respuestas de solo código se consideran de baja calidad: asegúrese de proporcionar una explicación de qué hace su código y cómo resuelve el problema. Ayudará tanto al autor de la pregunta como a los futuros lectores si puede agregar más información en su publicación. Ver Explicación de respuestas completamente basadas en código
Calos
1

Puedes hacerlo así:

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

Salida:

[[], [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]]
Blake
fuente
1
¿Puedo sugerir que al publicar una solución de código, tenga la amabilidad de dar una explicación detallada de lo que está haciendo el código y por qué utiliza este o aquel método para resolver un problema? Los nuevos programadores no deberían simplemente mirar un bloque de código y copiarlo / pegarlo sin saber exactamente qué está haciendo el código y por qué. Gracias y bienvenido a Stackoverflow.
Yokai
Una respuesta realmente impresionante y recursiva.
John
1

Sé que es demasiado tarde

Ya hay muchas otras soluciones, pero aún así ...

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
Nilesh Das
fuente
0

Esto es una locura porque ninguna de estas respuestas realmente proporciona el retorno de un conjunto de Python real. Aquí hay una implementación desordenada que dará un conjunto de poder que en realidad es un 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([]) ])

Sin embargo, me encantaría ver una mejor implementación.

Matthew Turner
fuente
Buen punto, pero el OP quiere una lista de conjuntos como salida, así que (en Python 3) puede hacerlo [*map(set, chain.from_iterable(combinations(s, r) for r in range(len(s)+1)))]; la función arg de mappuede ser frozensetsi lo prefiere.
PM 2 Ring
0

Aquí está mi implementación rápida utilizando combinaciones pero usando solo incorporados.

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
Daniel
fuente
0

Todos los subconjuntos en el rango n según lo establecido:

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 "{}")
Cestmoimahdi
fuente
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)
Subbhashit Mukherjee
fuente
0

Una variación de la pregunta es un ejercicio que veo en el libro "Descubriendo la informática: problemas interdisciplinarios, principios y programación en Python. Edición de 2015". En ese ejercicio 10.2.11, la entrada es solo un número entero y la salida deben ser los conjuntos de potencias. Aquí está mi solución recursiva (sin usar nada más que python3 básico)

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

Y la salida es

[[], [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]] Número de sublistas: 16

GeorgiosDoumas
fuente
0

No me había encontrado con la more_itertools.powersetfunción y recomendaría usarla. También recomiendo no usar el orden predeterminado de la salida de itertools.combinations, a menudo, en cambio, desea minimizar la distancia entre las posiciones y ordenar los subconjuntos de elementos con una distancia más corta entre ellos arriba / antes de los elementos con una mayor distancia entre ellos.

La itertoolspágina de recetas muestra que usachain.from_iterable

  • Tenga en cuenta que raquí coincide con la notación estándar para la parte inferior de un coeficiente binomial , sgeneralmente se hace referencia a él nen los textos de matemáticas y en las calculadoras ("n Elija 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))

Los otros ejemplos aquí dan el conjunto de potencias de [1,2,3,4]de tal manera que las 2 tuplas se enumeran en orden "lexicográfico" (cuando imprimimos los números como enteros). Si escribo la distancia entre los números a su lado (es decir, la diferencia), muestra mi punto:

121
132
143
231
242
341

El orden correcto para los subconjuntos debe ser el orden que 'agota' la distancia mínima primero, así:

121
231
341
132
242
143

El uso de números aquí hace que este orden parezca "incorrecto", pero considere, por ejemplo, las letras ["a","b","c","d"], es más claro por qué esto podría ser útil para obtener el conjunto de potencias en este orden:

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

Este efecto es más pronunciado con más elementos, y para mis propósitos marca la diferencia entre poder describir los rangos de los índices del conjunto de potencias de manera significativa.

(Hay mucho escrito en códigos Gray etc. para el orden de salida de los algoritmos en combinatoria, no lo veo como un problema secundario).

De hecho, acabo de escribir un programa bastante complicado que usó este código de partición de entero rápido para generar los valores en el orden correcto, pero luego descubrí more_itertools.powersety para la mayoría de los usos probablemente esté bien usar esa función así:

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)

Escribí algo de código más complicado que imprimirá el powerset muy bien (ver el repositorio de impresión bastante funciones que no he incluido aquí: print_partitions, print_partitions_by_length, y pprint_tuple).

Todo esto es bastante simple, pero aún puede ser útil si desea algún código que le permita acceder directamente a los diferentes niveles del conjunto de potencia:

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

Como ejemplo, escribí un programa de demostración CLI que toma una cadena como argumento de línea de comando:

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
Louis Maddox
fuente
0

Si desea una longitud específica de subconjuntos, puede hacerlo así:

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

De manera más general, para los subconjuntos de longitud arbitraria, puede modificar el rango de rango. La salida es

[(), (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 )]

Adel
fuente
-1
def powerset(some_set):
    res = [(a,b) for a in some_set for b in some_set]
    return res
Boz
fuente
6
Si bien este código puede responder a la pregunta, proporcionar un contexto adicional sobre por qué y / o cómo este código responde a la pregunta mejora su valor a largo plazo. Considere leer Cómo responder y editar su respuesta para mejorarla.
Blurfus
2
Lo que @blurfus es siempre una buena práctica, pero es especialmente importante cuando responde una pregunta de una década con otras 28 respuestas. ¿Por qué es esto una mejora con respecto a la respuesta aceptada? ¿Qué aporta esta respuesta que las otras respuestas no ofrecen?
Jeremy Caney