0

I have an algorithm that returns all possible combinations by taking one item from each column (Here a choice of soup, noodles and toppings).

Is there a more efficent and dynamic way to do this? For the findAllCombinations method to work, I need to know how many columns there are and hard code them.

Valid Combinations: [Watercress Soup, Udon, Fish Cube], [Spicy Soup, Ramen, Ham],...

ArrayList<ArrayList<String>> listOfLists = Lists.newArrayList();
listOfLists.add(Lists.newArrayList("Original Soup", "Spicy Soup", "Watercress Soup", "Thai Spicy Soup", "Malaysia Spicy Soup"));
listOfLists.add(Lists.newArrayList("Udon", "Ramen", "Egg Noodle", "Flat Rice Noodle", "Vermicelli", "Instant Noodle"));
listOfLists.add(Lists.newArrayList("Fish Cube", "Fish Ball", "Ham", "Squid", "Seaweed"));

ArrayList<ArrayList<String>> combo = findAllCombinations(listOfLists);


private ArrayList<ArrayList<String>> findAllCombinations(ArrayList<ArrayList<String>> arrays){
    ArrayList<ArrayList<String>> combinations = new ArrayList<>();
    for(String item1: arrays.get(0)){
        for(String item2: arrays.get(1)){
            for(String item3: arrays.get(2)){
                ArrayList<String> temp = new ArrayList<String>() {
                    {
                        add(item1);
                        add(item2);
                        add(item3);
                    }
                };
                combinations.add(temp);
            }
        }
    }
    return combinations;
}
PandaTank
  • 1
  • 2

2 Answers2

2

If you tweak your structure to be a list of sets instead of a list of lists, you can use Guava's Sets.cartesianProduct():

List<Set<String>> listOfSets = Lists.newArrayList();
listOfSets.add(Sets.newHashSet("Original Soup", "Spicy Soup", "Watercress Soup", "Thai Spicy Soup", "Malaysia Spicy Soup"));
listOfSets.add(Sets.newHashSet("Udon", "Ramen", "Egg Noodle", "Flat Rice Noodle", "Vermicelli", "Instant Noodle"));
listOfSets.add(Sets.newHashSet("Fish Cube", "Fish Ball", "Ham", "Squid", "Seaweed"));

Set<List<String>> combo = Sets.cartesianProduct(listOfSets);

If ordering is important, you can use LinkedHashSet.

EDIT: As of version 19, Guava has Lists.cartesianProduct(), which should do exactly what you want.

shmosel
  • 49,289
  • 6
  • 73
  • 138
2

If you don't use Guava and need/want to roll your own, here you go (code below).

The idea is to compute the number of combinations as the product of the list sizes, then iterate from 0 to number_of_combinations-1, and convert each integer in that range to a distinct combination.

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

public class Tester {

private static ArrayList<ArrayList<String>> findAllCombinations(ArrayList<ArrayList<String>> arrays){
    final ArrayList<ArrayList<String>> combinations = new ArrayList<>();
    int combinationCount = 1;
    for ( final ArrayList<String> als : arrays ) {
        combinationCount *= als.size();
    }

    for ( int i = 0; i < combinationCount; i++ ) {
        int combinationIndex = i;
        final ArrayList<String> oneCombination = new ArrayList<String>();
        for ( final ArrayList<String> als : arrays ) {
            int index = combinationIndex % als.size();
            oneCombination.add(als.get(index));
            combinationIndex = (combinationIndex - index) / als.size();
        }
        combinations.add(oneCombination);
    }
    return combinations;
}

public static void main(String[] args) {

final ArrayList<ArrayList<String>> listOfLists = new ArrayList<ArrayList<String>>();
listOfLists.add(new ArrayList<String>(Arrays.asList(new String[] {"Original Soup", "Spicy Soup", "Watercress Soup", "Thai Spicy Soup", "Malaysia Spicy Soup"})));
listOfLists.add(new ArrayList<String>(Arrays.asList(new String[] {"Udon", "Ramen", "Egg Noodle", "Flat Rice Noodle", "Vermicelli", "Instant Noodle"})));
listOfLists.add(new ArrayList<String>(Arrays.asList(new String[] {"Fish Cube", "Fish Ball", "Ham", "Squid", "Seaweed"})));

ArrayList<ArrayList<String>> combo = findAllCombinations(listOfLists);
System.out.println(combo);
System.out.println("Generated " + combo.size() + " combinations");
}

}
Matthew McPeak
  • 17,705
  • 2
  • 27
  • 59
  • 1
    OP clearly is using Guava. – shmosel May 31 '16 at 22:46
  • Yes, I know. But based on the title of the post, maybe not everyone who finds this question via search is using Guava. I can delete or revise my answer if that's a StackOverflow "foul". – Matthew McPeak May 31 '16 at 22:50
  • No problem with your answer (other than it being more complicated than OP's solution); I was just commenting on your first sentence. Side note: 1) `Arrays.asList()` accepts varargs, and 2) passing its result to a `new ArrayList` is a bit excessive, though it is *technically* required if you want to keep OP's method signature. – shmosel May 31 '16 at 22:54
  • Interesting Answer; I would not have thought of computing numbers first. – PandaTank May 31 '16 at 23:35