# Power Set Functions

• Use the position of 1 in bites to calculate all the possibilities of combinations in a list.

• like five bits 0 0 0 0 0 to indicate all the items' positions, and 1 can be every position in the bits. All the combinations should be the Powerset we want to, then use >> to check every combination's position whether it is 1 or not.

def powerSet(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
# >>j. move the bit we want to check to the end
# %2. remove all the other bits execpt the last one
# check the one we kept if it is 1 not 0,
# which means we want to keep the item which on the position
# example:  0 1 1 0 1
# we want to check the third "1"
# first move the second bit to the end(>>j), will be "0 0 0 1 1"
# then remove all the other bits(%2), we got "0 0 0 0 1"
# compare it with 1, which is true,
# so we take the item with the position, which will be item[2]
if (i >> j) % 2 == 1:
combo.append(items[j])
yield combo

• Use itertools.combinations to calculate r length subsequences of elements, then iterate with different length.

• the function of combinations is a little complicated, added some notations to help me to understand.

from itertools import chain

def powerset_generator(sets):
for subset in chain.from_iterable(combinations(sets, r) for r in range(len(sets)+1)):
yield subset

# the logic of this function is
#   set a new array with length r
#   loop the last element's index from i to i+n-r(n is the length of pool, r is the length of subsequence).
#   when hit the maximum which should be n-1, increase the last-1 element's index.
#   loop until the first element's index hit the maximum,
#   then increase the previous index, and set the last index to previous index + 1,
#   then back to the loop until all of the indices hit the maximum
# For example: iterable = [1,2,3,4,5], r = 3
#   (1, 2, 3)
#   (1, 2, 4)
#   (1, 2, 5) <-- the last index hit the maximum
#   (1, 3, 4) <-- increase the previous index, and set every one after to previous index + 1,
#   (1, 3, 5)
#   (1, 4, 5) <-- the (last-1) index hit the maximum
#   (2, 3, 4)
#   (2, 3, 5)
#   (2, 4, 5)
#   (3, 4, 5) <-- the (last-2) index hit the maximum
def combinations(iterable, r):

pool = tuple(iterable)

n = len(pool)

if r > n:
return
indices = list(range(r))

# In the "while" circle, we will start to change the indices by adding 1 consistently.
# So yield the first permutation before the while start.
yield tuple(pool[x] for x in indices)

while True:

# This 'for' loop is checking whether the index has hit the maximum from the last one to the first one.
# if it indices[i] >= its maximum,
#   set i = i-1, check the previous one
# if all of the indices has hit the maximum,
#   return the function
for i in reversed(range(r)):

# let's take an example to explain why using i + n - r
# pool indices: [0,1,2,3,4]
# subsequence indices: [0,1,2]
# so
#   indices[2] can be one of [2,3,4],
#   indices[1] can be one of [1,2,3],
#   indices[0] can be one of [0,1,2],
# and the range of every index is n-r, like this sample uses: 5-3=2
# then
#   indices[2] < 2+2 = i+2 = i+n-r,
#   indices[1] < 1+2 = i+2 = i+n-r,
#   indices[0] < 0+2 = i+2 = i+n-r,
# this can also be: indices[i] != i + n - r
if indices[i] < i + n - r:
break
else:
# loop finished, return
return

# Add one for current indices[i],
# (we already yield the first permutation before the loop)
indices[i] += 1
# this for loop increases every indices which is after indices[i].
# cause, current index has increased, and we need to confirm every one behind is initialized again.
# For example: current we got i = 2, indices[i]+1 will be 3,
# so the next loop should start with [1, 3, 4], not [1, 3, 3]
for j in range(i+1, r):
indices[j] = indices[j-1] + 1

yield tuple(pool[x] for x in indices)