Home > OS >  Convert itertools powerset into columnized numpy array
Convert itertools powerset into columnized numpy array

Time:11-03

Given a tuple items, I get the powerset with itertools:

items = tuple(('a', 'b', 'c'))
combos = powerset(items)

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

My goal is to convert the powerset output combos:

#combos =

(('a',), 
 ('b',), 
 ('c',), 
 ('a', 'b'), 
 ('a', 'c'), 
 ('b', 'c'), 
 ('a', 'b', 'c'))

into an np.array where for each row, each item is placed in the column corresponding to the location of that item in the original tuple cols.

Desired result:

#res = 

   [['a', '',   ''],
    ['',  'b',  ''],
    ['',  '',  'c'],
    ['a', 'b',  ''],
    ['a', '',  'c'],
    ['',  'b', 'c'],
    ['a', 'b', 'c']]

#shape = (7,3)

I was able to achieve this with the code below. However, is there a more pythonic, faster/memory efficient way to convert the output than my attempt where I loop through the array and place each individual item in the correct column in the result array?

Full code:

from itertools import combinations, chain
import numpy as np

def columnizePowerset(powerset, items):
    combo_arr = np.array([list(x) for x in powerset], dtype=object) # Convert to np.array
    res = np.empty([len(powerset), len(items)], dtype=str)          # Initialize result array

    for i in range(len(combo_arr)):                                 # Loop through rows
        for j in range(len(combo_arr[i])):                          # Loop through items in row
            col = np.where(np.array(items) == combo_arr[i][j])      # Identify column for item
            res[i][col] = combo_arr[i][j]                           # Place item in correct column

    return res

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

if __name__ == "__main__":
    items = tuple(('a', 'b', 'c'))                                  # Given
    combos = powerset(items)                                        # Perform powerset
    res = columnizePowerset(combos, items)                          # Convert powerset to columnized array
    print(res)

CodePudding user response:

You can check for all the bitmasks if the value is set that means we can include that in the current list else ignore it.

def powerset(s: list):
    res = []
    for mask in range(1, 2 ** len(s)):
        temp = ['' for _ in range(len(s))]
        for j in range(len(s)):
            if ((mask >> j) & 1) == 1:  # jth bit set in mask
                temp[j] = s[j]
        res.append(temp)
    return res


print(powerset(['a', 'b', 'c']))
[['a', '', ''], 
['', 'b', ''], 
['a', 'b', ''], 
['', '', 'c'], 
['a', '', 'c'], 
['', 'b', 'c'], 
['a', 'b', 'c']]

CodePudding user response:

The order isn't exactly the same as powerset yields, but the pattern reminded me of binary – you can get the same items by looking at the bits of increasing integers...

I'm not a NumPy wizard, but this seems reasonably numpy-er. (See post edit history for worse solutions.)

import numpy as np


def powerset_numpy(items):
    n = len(items)
    bit_indexes = np.arange(1, 2 ** n)
    n_bits = bit_indexes.shape[0]
    item_bits = np.repeat(np.arange(0, n), n_bits).reshape((n, -1))
    item_values = np.repeat(items, n_bits).reshape((n, -1))
    n_bits = (bit_indexes & (1 << item_bits)).astype(bool)
    return np.where(n_bits, item_values, '').T

print(powerset_numpy(['a', 'b', 'c']))

prints out

[['a' '' '']
 ['' 'b' '']
 ['a' 'b' '']
 ['' '' 'c']
 ['a' '' 'c']
 ['' 'b' 'c']
 ['a' 'b' 'c']]
  • Related