Search code examples
javasetpowerset

Power set of an input set as custom collection


I have been reading the Effective Java book and I have stuck with this code I am unable to understand how this code is generating power set.

Code:

public class PowerSet {

    public static final <E> Collection<Set<E>> of(Set<E> s) {
        List<E> src = new ArrayList<>(s);
        if (src.size() >= 30)
            throw new IllegalArgumentException("Set too big " + s);
        return new AbstractList<Set<E>>() {

            @Override
            public int size() {
                return 1 << src.size();
            }

            @Override
            public boolean contains(Object o) {
                return o instanceof Set && src.containsAll((Set) o);
            }

            @Override
            public Set<E> get(int index) {
                Set<E> result = new HashSet<>();
                for (int i = 0; index != 0; i++, index >>= 1)
                    if ((index & 1) == 1)
                        result.add(src.get(i));
                return result;
            }
        };
    }

    public static void main(String[] args) {
        Collection<Set<String>> result = of(Set.of("a", "b", "c"));
        System.out.println(result);
    }
}

Output:

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

Can someone explain how this code is generating powerset of a given set.


Solution

  • The code uses the binary representation of the index number as a map of which element of s to include.

    For instance, assuming only 3 bits in a number:

    index   | a | b | c
    --------------------
    0 (000) | 0 | 0 | 0 -> take nothing
    1 (001) | 0 | 0 | 1 -> take only c
    2 (010) | 0 | 1 | 0 -> take only b
    3 (011) | 0 | 1 | 1 -> take a and b
    4 (100) | 1 | 0 | 0 -> take only a
    ...
    

    The get method of the generated list follows this logic with the index input given:

    • index >>= 1 shifts all bits one position to the right with each loop
    • (index & 1) == 1 checks whether the rightmost bit of index is a 1

    The & operator is the binary AND, so 2 & 1 equals binary 010 AND 001, giving 000 (not equal to 1 or 001) and 3 & 1 equals binary 011 AND 001, giving 001 (equal to 1 or 001)

    • If this evaluates to true, the i-th element is added to the list
    • This terminates when index == 0, i.e. there are no more bits to shift / elements to add

    Example for index = 3:

    i | index | (index & 1) == 1 | element added
    ---------------------------------------------
    0 |   011 |             TRUE |   a (0-th element)
    1 |   001 |             TRUE |   b (1-th element)
    2 |   000 |            FALSE |   - 
    (terminates as index == 0)