views:

529

answers:

5

I want this method to work for any given number of arguments, i can do that with code generation(with a lot of ugly code), can it be done with recursion? if so how? I understand recursion, but i dont know how to write this.

private static void allCombinations(List<String>... lists) {
 if (lists.length == 3) {
  for (String s3 : lists[0]) {
   for (String s1 : lists[1]) {
    for (String s2 : lists[2]) {
     System.out.println(s1 + "-" + s2 + "-" + s3);
    }
   }
  }
 }
 if (lists.length == 2) {
  for (String s3 : lists[0]) {
   for (String s1 : lists[1]) {
     System.out.println(s1 + "-" + s3);
   }
  }
 }
}
+1  A: 

Do you particularly need it to be recursive? I'd make it non-recursive but still not special case things:

public static void allCombinations(List<String>... lists) {
    int[] indexes = new int[lists.length];

    while (incrementIndexes(lists, indexes)) {
        StringBuilder builder = new StringBuilder();
        for (int i=0; i < indexes.length; i++) {
            if (i != 0) {
                builder.append("-");
            }
            builder.append(lists[i].get(indexes[i]));
        }
        System.out.println(builder);
    }
}

private static boolean incrementIndexes(List<String>[] lists, int[] indexes) {
    for (int depth = indexes.length-1; depth >= 0; depth--) {
        indexes[depth]++;
        if (indexes[depth] != lists[depth].size()) {
            return true;
        }
        // Overflowed this index. Reset to 0 and backtrack
        indexes[depth] = 0;
    }
    // Everything is back to 0. Finished!
    return false;
}
Jon Skeet
To some extent you have emulated what a recursive solution would do anyway (by managing a list of indexes). However, I expect that your solution is more efficient but possibly a little less readable than the recursive equivalent.
Richard Dorman
It's certainly effectively emulating the recursive version. In this particular case, the solution given by Rasmus is simpler because it's easy to capture the "current state" as a string, and then append to it. In a more general case of needing each of the current items I suspect (continued)
Jon Skeet
you'd have to keep a collection of indexes (or strings) anyway - it doesn't make much difference whether it's recursive or not at that point. I'll try to come up with an alternative recursive form :)
Jon Skeet
Generally recursive solutions incur more overhead than their non-recursive counterparts (mostly in the way of stack allocations) but are often easier to read (Rasmus's solution being a great example).
Richard Dorman
+3  A: 

Here is a simple recursive implementation:

private static void allCombinations(List<String>... lists) {
  allCombinations(lists, 0, "");
}

private static void allCombinations(List<String>[] lists, int index, String pre) {
  for (String s : lists[index]) {
    if (index < lists.length - 1) {
      allCombinations(lists, index + 1, pre + s + "-");
    }else{
      System.out.println(pre + s);
    }
  }
}
Rasmus Faber
this does not give the result that the original code gave. notice the s3,s1,s2 order of the loops.
Amir Arad
Ah, I assumed that was an error. Anyway, this should be the general principle of a recursive solution. You can perform some transformation to the index-variable before using it in the line with the for-statement to obtain another ordering of the loops.
Rasmus Faber
A: 

Here's a generalised recursive version. It complains about unchecked generic array creation in the test code, but the permute code itself is okay:

import java.util.*;

public class Test
{
    public interface Action<T> {
        void execute(Iterable<T> values);
    }

    public static void main(String[] args) {
        List<String> first = Arrays.asList(new String[]{"1", "2", "3"});
        List<String> second = Arrays.asList(new String[]{"a", "b", "c"});
        List<String> third = Arrays.asList(new String[]{"x", "y"});
        Action<String> action = new Action<String>() {
            @Override public void execute(Iterable<String> values) {
                 StringBuilder builder = new StringBuilder();
                 for (String value : values) {
                     if (builder.length() != 0) {
                         builder.append("-");
                     }
                     builder.append(value);
                 }
                 System.out.println(builder);
            }
        };
        permute(action, first, second, third);
    }

    public static <T> void permute(Action<T> action, Iterable<T>... lists) {
        Stack<T> current = new Stack<T>();
        permute(action, lists, 0, current);
    }

    public static <T> void permute(Action<T> action, Iterable<T>[] lists,
        int index, Stack<T> current) {
        for (T element : lists[index]) {
            current.push(element);
            if (index == lists.length-1) {
              action.execute(current);
            } else {
              permute(action, lists, index+1, current);
            }
            current.pop();
        }
    }
}
Jon Skeet
A: 

here's my recursive solution with correct ordering, based on Rasmus' solution. it works only if all lists are of same size.

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


public class Test {

        public static void main(String[] args) {
        List<String> first = Arrays.asList(new String[]{"1", "2", "3"});
        List<String> second = Arrays.asList(new String[]{"a", "b", "c"});
        List<String> third = Arrays.asList(new String[]{"x", "y", "z"});
        allCombinations (first, second, third);
        }

        private static void allCombinations(List<String>... lists) {
           allCombinations(lists, 1, "");
        }

        private static void allCombinations(List<String>[] lists, int index, String pre) {
         int nextHop = hop(index, lists.length-1);
          for (String s : lists[index]) {
            if (index != 0) {
              allCombinations(lists, nextHop, pre + s + "-");
            } else System.out.println(pre + s);
          }
        }
        private static int hop(int prevIndex, int maxResult){
         if (prevIndex%2 == 0){
          return prevIndex-2;
         } else {
          if (prevIndex == maxResult) 
           return prevIndex-1;
          int nextHop = prevIndex+2;
          if (nextHop > maxResult){
           return maxResult;
          } else return nextHop;
         }
        }

}

a "correct ordering" solution that allows lists of different sizes will have to start from the last list and work it's way backwards to the first list (lists[0]), appending the element at either beginning or end of the "pre" string and passing it onward. again, the first list will print the result. I'd have coded that, but lunch is ready and girlfriend is beginning to dislike stackoverflow...

Amir Arad
A: 

thx for help guys, all solutions look good, i though problem like this needs use of recursion, but now i see its just one of the options. thx again.