Search code examples
javaalgorithmsubsetbitpowerset

Printing PowerSet with help of bit position


Googling around for a while to find subsets of a String, i read wikipedia and it mentions that

.....For the whole power set of S we get:

{ } = 000 (Binary) = 0 (Decimal)
{x} = 100 = 4
{y} = 010 = 2
{z} = 001 = 1
{x, y} = 110 = 6
{x, z} = 101 = 5
{y, z} = 011 = 3
{x, y, z} = 111 = 7

Is there a possible way to implement this through program and avoid recursive algorithm which uses string length?

What i understood so far is that, for a String of length n, we can run from 0 to 2^n - 1 and print characters for on bits.
What i couldn't get is how to map those on bits with the corresponding characters in the most optimized manner

PS : checked thread but couldnt understood this and c++ : Power set generated by bits


Solution

  • The idea is that a power set of a set of size n has exactly 2^n elements, exactly the same number as there are different binary numbers of length at most n.

    Now all you have to do is create a mapping between the two and you don't need a recursive algorithm. Fortunately with binary numbers you have a real intuitive and natural mapping in that you just add a character at position j in the string to a subset if your loop variable has bit j set which you can easily do with getBit() I wrote there (you can inline it but for you I made a separate function for better readability).

    P.S. As requested, more detailed explanation on the mapping: If you have a recursive algorithm, your flow is given by how you traverse your data structure in the recursive calls. It is as such a very intuitive and natural way of solving many problems.

    If you want to solve such a problem without recursion for whatever reason, for instance to use less time and memory, you have the difficult task of making this traversal explicit.

    As we use a loop with a loop variable which assumes a certain set of values, we need to make sure to map each value of the loop variable, e.g. 42, to one element, in our case a subset of s, in a way that we have a bijective mapping, that is, we map to each subset exactly once. Because we have a set the order does not matter, so we just need whatever mapping that satisfies these requirements.

    Now we look at a binary number, e.g. 42 = 32+8+2 and as such in binary with the position above:

    543210
    101010
    

    We can thus map 42 to a subset as follows using the positions:

    • order the elements of the set s in any way you like but consistently (always the same in one program execution), we can in our case use the order in the string
    • add an element e_j if and only if the bit at position j is set (equal to 1).

    As each number has at least one digit different from any other, we always get different subsets, and thus our mapping is injective (different input -> different output).

    Our mapping is also valid, as the binary numbers we chose have at most the length equal to the size of our set so the bit positions can always be assigned to an element in the set. Combined with the fact that our set of inputs is chosen to have the same size (2^n) as the size of a power set, we can follow that it is in fact bijective.

    import java.util.HashSet;
    import java.util.Set;
    
    public class PowerSet
    {
    
        static boolean getBit(int i, int pos) {return (i&1<<pos)>0;}
    
        static Set<Set<Character>> powerSet(String s)
        {
            Set<Set<Character>> pow = new HashSet<>();
            for(int i=0;i<(2<<s.length());i++)
            {
                Set<Character> subSet = new HashSet<>();
                for(int j=0;j<s.length();j++)
                {
                    if(getBit(i,j)) {subSet.add(s.charAt(j));}
                }
                pow.add(subSet);
            }
            return pow;
    
        }
    
        public static void main(String[] args)
        {System.out.println(powerSet("xyz"));}
    
    }