Obtaining a powerset of a set in Java

后端 未结 26 1703
青春惊慌失措
青春惊慌失措 2020-11-22 11:33

The powerset of {1, 2, 3} is:

{{}, {2}, {3}, {2, 3}, {1, 2}, {1, 3}, {1, 2, 3}, {1}}

Let\'s say I have a Set in Java:<

相关标签:
26条回答
  • 2020-11-22 11:50
    // input: S
    // output: P
    // S = [1,2]
    // P = [], [1], [2], [1,2]
    
    public static void main(String[] args) {
        String input = args[0];
        String[] S = input.split(",");
        String[] P = getPowerSet(S);
        if (P.length == Math.pow(2, S.length)) {
            for (String s : P) {
                System.out.print("[" + s + "],");
            }
        } else {
            System.out.println("Results are incorrect");
        }
    }
    
    private static String[] getPowerSet(String[] s) {
        if (s.length == 1) {
            return new String[] { "", s[0] };
        } else {
            String[] subP1 = getPowerSet(Arrays.copyOfRange(s, 1, s.length));
            String[] subP2 = new String[subP1.length];
            for (int i = 0; i < subP1.length; i++) {
                subP2[i] = s[0] + subP1[i];
            }
            String[] P = new String[subP1.length + subP2.length];
            System.arraycopy(subP1, 0, P, 0, subP1.length);
            System.arraycopy(subP2, 0, P, subP1.length, subP2.length);
            return P;
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 11:51

    I came up with another solution based on @Harry He's ideas. Probably not the most elegant but here it goes as I understand it:

    Let's take the classical simple example PowerSet of S P(S) = {{1},{2},{3}}. We know the formula to get the number of subsets is 2^n (7 + empty set). For this example 2^3 = 8 subsets.

    In order to find each subset we need to convert 0-7 decimal to binary representation shown in the conversion table below:

    ConversionTable

    If we traverse the table row by row, each row will result in a subset and the values of each subset will come from the enabled bits.

    Each column in the Bin Value section corresponds to the index position in the original input Set.

    Here my code:

    public class PowerSet {
    
    /**
     * @param args
     */
    public static void main(String[] args) {
        PowerSet ps = new PowerSet();
        Set<Integer> set = new HashSet<Integer>();
        set.add(1);
        set.add(2);
        set.add(3);
        for (Set<Integer> s : ps.powerSet(set)) {
            System.out.println(s);
        }
    }
    
    public Set<Set<Integer>> powerSet(Set<Integer> originalSet) {
        // Original set size e.g. 3
        int size = originalSet.size();
        // Number of subsets 2^n, e.g 2^3 = 8
        int numberOfSubSets = (int) Math.pow(2, size);
        Set<Set<Integer>> sets = new HashSet<Set<Integer>>();
        ArrayList<Integer> originalList = new ArrayList<Integer>(originalSet);
        for (int i = 0; i < numberOfSubSets; i++) {
            // Get binary representation of this index e.g. 010 = 2 for n = 3
            String bin = getPaddedBinString(i, size);
            //Get sub-set
            Set<Integer> set = getSet(bin, originalList));
            sets.add(set);
        }
        return sets;
    }
    
    //Gets a sub-set based on the binary representation. E.g. for 010 where n = 3 it will bring a new Set with value 2
    private Set<Integer> getSet(String bin, List<Integer> origValues){
        Set<Integer> result = new HashSet<Integer>();
        for(int i = bin.length()-1; i >= 0; i--){
            //Only get sub-sets where bool flag is on
            if(bin.charAt(i) == '1'){
                int val = origValues.get(i);
                result.add(val);
            }
        }
        return result;
    }
    
    //Converts an int to Bin and adds left padding to zero's based on size
    private String getPaddedBinString(int i, int size) {
        String bin = Integer.toBinaryString(i);
        bin = String.format("%0" + size + "d", Integer.parseInt(bin));
        return bin;
    }
    
    }
    
    0 讨论(0)
  • 2020-11-22 11:51

    This is my approach with lambdas.

    public static <T> Set<Set<T>> powerSet(T[] set) {
          return IntStream
                .range(0, (int) Math.pow(2, set.length))
                .parallel() //performance improvement
                .mapToObj(e -> IntStream.range(0, set.length).filter(i -> (e & (0b1 << i)) != 0).mapToObj(i -> set[i]).collect(Collectors.toSet()))
                .map(Function.identity())
                .collect(Collectors.toSet());
            }
    

    Or in parallel (see parallel() comment):

    Size of input set: 18

    Logical processors: 8 à 3.4GHz

    Performance improvement: 30%

    0 讨论(0)
  • 2020-11-22 11:51

    I recently had to use something like this, but needed the smallest sublists (with 1 element, then 2 elements, ...) first. I did not want to include the empty nor the whole list. Also, I did not need a list of all the sublists returned, I just needed to do some stuff with each.

    Wanted to do this without recursion, and came up with the following (with the "doing stuff" abstracted into a functional interface):

    @FunctionalInterface interface ListHandler<T> {
        void handle(List<T> list);
    }
    
    
    public static <T> void forAllSubLists(final List<T> list, ListHandler handler) {
        int     ll = list.size();   // Length of original list
        int     ci[] = new int[ll]; // Array for list indices
        List<T> sub = new ArrayList<>(ll);  // The sublist
        List<T> uml = Collections.unmodifiableList(sub);    // For passing to handler
    
        for (int gl = 1, gm; gl <= ll; gl++) {  // Subgroup length 1 .. n-1
            gm = 0; ci[0] = -1; sub.add(null);  // Some inits, and ensure sublist is at least gl items long
    
            do {
                    ci[gm]++;                       // Get the next item for this member
    
                    if (ci[gm] > ll - gl + gm) {    // Exhausted all possibilities for this position
                            gm--; continue;         // Continue with the next value for the previous member
                    }
    
                    sub.set(gm, list.get(ci[gm]));  // Set the corresponding member in the sublist
    
                    if (gm == gl - 1) {             // Ok, a sublist with length gl
                            handler.handle(uml);    // Handle it
                    } else {
                            ci[gm + 1] = ci[gm];    // Starting value for next member is this 
                            gm++;                   // Continue with the next member
                    }
            } while (gm >= 0);  // Finished cycling through all possibilities
        }   // Next subgroup length
    }
    

    In this way, it's also easy to limit it to sublists of specific lengths.

    0 讨论(0)
  • 2020-11-22 11:52

    Yet another solution - with java8+ streaming api It is lazy and ordered so it returns correct subsets when it is used with "limit()".

     public long bitRangeMin(int size, int bitCount){
        BitSet bs = new BitSet(size);
        bs.set(0, bitCount);
        return bs.toLongArray()[0];
    }
    
    public long bitRangeMax(int size, int bitCount){
        BitSet bs = BitSet.valueOf(new long[]{0});
        bs.set(size - bitCount, size);
        return bs.toLongArray()[0];
    }
    
    public <T> Stream<List<T>> powerSet(Collection<T> data)
    {
        List<T> list = new LinkedHashSet<>(data).stream().collect(Collectors.toList());
        Stream<BitSet> head = LongStream.of(0).mapToObj( i -> BitSet.valueOf(new long[]{i}));
        Stream<BitSet> tail = IntStream.rangeClosed(1, list.size())
                .boxed()
                .flatMap( v1 -> LongStream.rangeClosed( bitRangeMin(list.size(), v1), bitRangeMax(list.size(), v1))
                        .mapToObj(v2 -> BitSet.valueOf(new long[]{v2}))
                        .filter( bs -> bs.cardinality() == v1));
    
        return Stream.concat(head, tail)
                .map( bs -> bs
                        .stream()
                        .mapToObj(list::get)
                        .collect(Collectors.toList()));
    }
    

    And the client code is

    @Test
    public void testPowerSetOfGivenCollection(){
        List<Character> data = new LinkedList<>();
        for(char i = 'a'; i < 'a'+5; i++ ){
            data.add(i);
        }
        powerSet(data)
                .limit(9)
                .forEach(System.out::print);
    
    }
    

    /* Prints : [][a][b][c][d][e][a, b][a, c][b, c] */

    0 讨论(0)
  • 2020-11-22 11:52

    We could write the power set with or without using recursion. Here is an attempt without recursion:

    public List<List<Integer>> getPowerSet(List<Integer> set) {
        List<List<Integer>> powerSet = new ArrayList<List<Integer>>();
        int max = 1 << set.size();
        for(int i=0; i < max; i++) {
            List<Integer> subSet = getSubSet(i, set);
            powerSet.add(subSet);
        }
        return powerSet;
    }
    
    private List<Integer> getSubSet(int p, List<Integer> set) {
        List<Integer> subSet = new ArrayList<Integer>();
        int position = 0;
        for(int i=p; i > 0; i >>= 1) {
            if((i & 1) == 1) {
                subSet.add(set.get(position));
            }
            position++;
        }
        return subSet;
    }
    
    0 讨论(0)
提交回复
热议问题