7

Is there an efficient way to find all possible combinations between multiple enums in Java?

Consider the following three enums -

public enum EnumOne {
   One ("One"),
   OneMore ("OneMore");
}

public enum EnumTwo {
   Two ("Two"),
}

public enum EnumThree {
   Three ("Three"),
   ThreeMore ("ThreeMore");
}

I would like the output to produce all possible combinations between these multiple enums i.e.

{EnumOne.One, EnumTwo.Two, EnumThree.Three},
{EnumOne.One, EnumTwo.Two, EnumThree.ThreeMore},
{EnumOne.OneMore, EnumTwo.Two, EnumThree.Three},
{EnumOne.OneMore, EnumTwo.Two, EnumThree.ThreeMore}

Hoping to find an effective way of handling it.

Thanks

Travis Heeter
  • 13,002
  • 13
  • 87
  • 129
JUG
  • 693
  • 9
  • 25
  • Are you looking for something better than nested loops? It's not clear what you are asking – Misha May 01 '15 at 19:27
  • As less recursive as possible – JUG May 01 '15 at 19:27
  • 5
    Guava has some neat functions for powersets and permutations.... – Ueli Hofstetter May 01 '15 at 19:27
  • See here https://stackoverflow.com/questions/29172066/generate-all-permutations-of-several-lists-in-java/29172997#29172997. Works for enums if you use `Arrays.asList(EnumOne.values())` and the like. – dhke May 01 '15 at 20:10
  • Any solution better than O(K x M x N)? – LHA May 01 '15 at 20:14
  • @LocHa The number of combinations is exponential in the length of the input lists (actually, `len(l1) * len(l2) * ... * len(ln)`). Generation can be done with linear space in the number of input enums, but you will always get an exponential number of results. – dhke May 01 '15 at 20:18
  • it's not exponential, it's combinatorial (or factorial depending on nomenclature) – Transcendence May 01 '15 at 21:03
  • @Transcendence What complexity class is *combinatorial*? The number of results is clearly exponential in the length of the input lists. `n!` would apply for *non-repeating* combinations from the *same* list. – dhke May 01 '15 at 21:27
  • whoops i didn't reduce it. it's n_1 choose 1 * n_2 choose 1 *... n_m choose 1, but that reduces to n_1 *... * n_m. but thats polynomial, not exponential. exponential would be (some constant)^n – Transcendence May 01 '15 at 21:41
  • combinatorial is O(n!) – Transcendence May 01 '15 at 21:42
  • @Transcendence let's say it's *exponential* in the number of input lists, which makes it *polynomial* in the number of elements, when the number of lists is finite. I think that should make both of us happy. – dhke May 01 '15 at 21:45
  • Seek you a solution for any number of enum classes, or exactly 3? Is code efficiency (ie less code) or execution efficiency the goal (you probably can't have both) – Bohemian May 02 '15 at 03:11
  • Any number of enums, in this case three is example. Execution efficiency is the goal. – JUG May 02 '15 at 13:01

3 Answers3

1

the complexity of the algorithms is O(NxMxK .... xZ) if I'm wrong, I don't know if it an "efficient way" .... I use as a backtraking solution

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ProductEnums {

    public enum EnumOne {
        One,
        OneMore;
    }

    public enum EnumTwo {
        Two,
    }

    public enum EnumThree {
        Three,
        ThreeMore;
    }

    public static void main(String[] args) {
        // pass each values in enums
        List a = product(EnumOne.values(),
                EnumTwo.values(), EnumThree.values());
        System.out.println(a);
    }

    public static List<List<Enum>> product(Enum[]... enums) {
        return product(new ArrayList<>(Arrays.asList(enums)));
    }

    public static List<List<Enum>> product(List<Enum[]> enums) {
        if (enums.isEmpty()) {
            //Trivial case of recursive function
            return new ArrayList<>();
        }
        //remove first element
        Enum[] myEnums = enums.remove(0);
        List<List<Enum>> out = new ArrayList<>();
        for (Enum e : myEnums) {
            //call recursive
            List<List<Enum>> list = product(enums);
            for (List<Enum> list_enum : list) {
                //for each list get from recursion adding element e
                list_enum.add(0, e);
                out.add(list_enum);
            }
            if(list.isEmpty()){
                List<Enum> list_enum = new ArrayList<>();
                list_enum.add(e);
                out.add(list_enum);
            }
        }
        enums.add(0, myEnums); //Backtraking
        return out;
    }
}

Result

[[One, Two, Three], [One, Two, ThreeMore], [OneMore, Two, Three], [OneMore, Two, ThreeMore]]

Jose Ricardo Bustos M.
  • 8,016
  • 6
  • 40
  • 62
  • No need to use `ArrayList` in the declarations, `List` is enough. It's also unsafe code in multiple locations. I'm also not sure if this counts as *efficient* as it stores the whole (possibly large list) in memory. As said, it can be done in linear space. – dhke May 01 '15 at 21:35
  • @dhke I fixed List in declarations ..... you are rigth with memory use is inefficient in this code, and this can be improved – Jose Ricardo Bustos M. May 01 '15 at 22:08
  • @dhke a algorithm better is used in [cartesian-product-of-arbitrary-sets-in-java](http://stackoverflow.com/questions/714108/cartesian-product-of-arbitrary-sets-in-java), but also they use an intermediate list as part of the solution – Jose Ricardo Bustos M. May 01 '15 at 22:19
  • I've linked my version of a solution in the comments that works in linear space but only for indexeds lists (which enum value are) and of course it requires a callback. I'd really like to see an iterator-based one that also returns an iterator, but I think that becomes quite complex to get right. – dhke May 01 '15 at 22:23
0

Here is an iterator based solution. This way, the memory consumption won’t explode if it operates on many enum types with loads of enum constants. Execution efficiency should therefore be fine (furthermore the implementation avoids recursion).

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class EnumCombination implements Iterable<Enum<?>[]> {

    private final Enum<?>[][] enumConstants;
    private final int[] limits;
    private final boolean emptyCombination;

    public EnumCombination(final List<Class<? extends Enum<?>>> enums) {
        this.limits = new int[enums.size()];
        this.enumConstants = new Enum<?>[enums.size()][];

        boolean empty = enums.isEmpty();
        for (int i = 0; i < enums.size(); i++) {
            final Enum<?>[] enumElements = enums.get(i).getEnumConstants();
            enumConstants[i] = enumElements;
            limits[i] = enumElements.length - 1;
            empty |= enumElements.length == 0;
        }
        this.emptyCombination = empty;
    }

    @Override
    public Iterator<Enum<?>[]> iterator() {
        return new EnumCombinationIterator();
    }

    private class EnumCombinationIterator implements Iterator<Enum<?>[]> {
        private final int[] cursors = new int[limits.length];
        private boolean exhausted = emptyCombination;

        @Override
        public boolean hasNext() {
            return !exhausted;
        }

        @Override
        public Enum<?>[] next() {
            if (exhausted)
                throw new NoSuchElementException();

            final Enum<?>[] result = new Enum<?>[cursors.length];
            for (int i = 0; i < cursors.length; i++) {
                result[i] = enumConstants[i][cursors[i]];
            }
            moveCursors();

            return result;
        }

        private void moveCursors() {
            for (int i = cursors.length - 1; i >= 0; i--) {
                cursors[i] = cursors[i] == limits[i] ? 0 : cursors[i] + 1;
                if (cursors[i] != 0) {
                    break;
                } else if (i == 0) {
                    exhausted = true;
                }
            }
        }
    }
}

EnumCombination can be used like this:

import java.util.*;

public class Main {

    public enum EnumOne {
        One,
        OneMore
    }

    public enum EnumTwo {
        Two
    }

    public enum EnumThree {
        Three,
        ThreeMore
    }

    public static void main(String... args) {
        EnumCombination enumCombination = new EnumCombination(
                Arrays.asList(EnumOne.class, EnumTwo.class, EnumThree.class));

        for (final Enum<?>[] ec : enumCombination) {
            System.out.println(Arrays.toString(ec));
        }
    }
}

But, of course, one could use guava’s cartesianProduct() as well …

user6368437
  • 116
  • 4
-1

How about something like this.

void printAll(List<Class> enums, int i, String[] msg) {
    if (!enums.get(i).isEnum()) {
        throw new IllegalStateException();
    }
    Object[] enumsConstants = enums.get(i).getEnumConstants();
    if (i == 0) {
        //first iteration
        for (Object o : enumsConstants) {
            if (enums.size() == 1) {
                System.out.println("{ " + o.toString() + " }");
            } else {
                msg = new String[enums.size()];
                msg[0] = "{ " + o.toString();
                printAll(enums, i + 1, msg);
            }
        }
    } else if (i == enums.size() - 1) {
        //on the last iteration
        for (Object o : enumsConstants) {
            msg[i] = ", " + o.toString() + " }";
            System.out.println(Arrays.toString(msg));
        }
    } else {
        //middle iteration
        for (Object o : enumsConstants) {
            msg[i] = ", " + o.toString();
            printAll(enums, i + 1, msg);
        }
    }
}

You would use it like this

printAll(allMyEnumClassesList, 0, null);
Jose Martinez
  • 11,452
  • 7
  • 53
  • 68
  • That puts the values into a string. I'm not so sure that is what is requested. – dhke May 01 '15 at 20:20
  • @dhke thats left up to interpretation. Either way the output can be altered if need be. Does not need to be a String[], can be Object[]. And it does not need to be printed when it gets a combo (it can store it somewhere if need be). – Jose Martinez May 01 '15 at 20:23