I want to split a list into a given number n sublists in all possible ways in Java.
For example [1, 2, 3, 4]
where n = 3 would include the following lists (but would not be a complete solution - complete would require much more space):
([], [], [1,2,3,4])
([],[1],[2,3,4])
([],[1,2],[3,4])
([],[1,2,3],[4])
([],[1,2,3,4],[])
([1],[2,3,4], [])
([1],[2,3],[4])
([2,3],[4],[1])
([4],[],[1,2,3])
...
etc
I adapted a solution from another similar question (Split a list into two sublists in all possible ways) however it only works for creating lists of 2 sublists and I am struggling to grasp how to implement it for a flexible rather than hardcoded number of sublists.
Here is my code:
public List<List<EGroup>> permutation(List<E> list) {
List<List<E>> sublists = new ArrayList<List<E>>();
for (int i = 0; i <= list.size(); i++) {
permutationSplit(list, sublists, i, new ArrayList<E>(), 0);
}
List<List<EGroup>> listOfEGroupPairs = new ArrayList<List<EGroup>>();
for (List<E> subList : sublists) {
List<E> listCopy = new ArrayList<E>(list);
listCopy.removeAll(subList);
EGroup e1 = new EGroup(subList);
EGroup e2 = new EGroup(listCopy);
List<EGroup> egr = new ArrayList<EGroup>();
egr.add(e1);
egr.add(e2);
listOfEGroupPairs.add(egr);
}
return listOfEGroupPairs;
}
public void permutationSplit(List<E> list, List<List<E>> subLists, int sublistSize, List<E> currentSubList,
int startIndex) {
if (sublistSize == 0) {
subLists.add(currentSubList);
} else {
sublistSize--;
for (int i = startIndex; i < list.size(); i++) {
List<E> newSubList = new ArrayList<E>(currentSubList);
newSubList.add(list.get(i));
permutationSplit(list, subLists, sublistSize, newSubList, i + 1);
}
}
}
I need to create n number of EGroup objects to add to listOfEGroupPairs
rather than the hardcoded 2, but how to always get the right number (n) of sublists of varied size each loop?
If I understand the question correctly, each element of the original list can end up in any of the n
sublists. That means, there are n^s
possible sublists (s
being the number of elements in the original list), which can be enumerated in a simple loop. With a bit of modulo and integer division you can then get the proper "bucket" for each element and prepare the results accordingly.
public <T> List<List<List<T>>> partition(List<T> lst, int n) {
var result = new ArrayList<List<List<T>>>();
// k = SUM ( pos of lst[i] * n^i )
for (int k = 0; k < Math.pow(n, lst.size()); k++) {
// initialize result
List<List<T>> res = IntStream.range(0, n)
.mapToObj(i -> new ArrayList<T>())
.collect(Collectors.toList());
// distribute elements to sub-lists
int k2 = k;
for (int i = 0; i < lst.size(); i++) {
res.get(k2 % n).add(lst.get(i));
k2 /= n;
}
result.add(res);
}
return result;
}