you can solve this by computing all the possibilities first and then just sort them. Finding all the possibilities is not that complicated if you can spot an interesting rule. Let's take an example:
a
=======
[a]
If you have a single input a
, the only possible result is [a]
.
a, b
==============
[a] [a, b]
[b]
If you have an input as a, b
, there are 3 possible results: [a], [b], [a, b]
.
This is where we spot rule 1 : the possibilities of that single element ([a]
) are a subset of possibilities in a, b
. Or generalized:
all the possibilities from the previous result are going to be contained in the current one.
Let's see if that holds true for a, b, c
:
a, b a, b, c
================== =============================
-------------- -------------
| [a] [a, b] | | [a] [a, b] |
| [b] | | [b] |
|--------------| |-------------| [a, b, c]
[c] [a, c]
[b, c]
You can verify this for much larger inputs, but it always holds true. And if you look in perspective, this all makes sense. All combinations of a potential n
will be : all combinations of n-1
+ some more.
But there is another rule here that is more interesting.
If you have such an input:
a, b
==============
[a] [a, b]
[b]
you can create an algorithm on how to compute the next one. First of all create a copy of the previous one (because of the rule above):
a, b, c
==============
[a] [a, b]
[b]
For the first row, all you need to add is the "last" next letter. Since the next one is going to a, b, c
, all you need to add to the first row is : c
:
a, b, c
==============
[a] [a, b]
[b]
[c]
For the second row, you take the previous row (minus the c
that was added), append the "last" next letter and insert it. That is :
a, b, c
===================
[a] <-| [a, b]
[b] |-> [a, c] <-- "a" came from the previous row, "c" was added as the last one
[c]
Some for b
:
a, b, c
===================
[a] [a, b]
[b] <-| [a, c]
[c] |-> [b, c] <-- "b" came from the previous row, "c" then added as the last one
For the last row, we simply add all the "next one" (a, b, c
):
a, b, c
=================================
[a] [a, b]
[b] [a, c] [a, b, c]
[c] [b, c]
With the same logic above, you can compute the result for a, b, c, d
:
Copy first:
a, b, c, d
=================================
[a] [a, b]
[b] [a, c] [a, b, c]
[c] [b, c]
Add d
:
a, b, c, d
=================================
[a] [a, b]
[b] [a, c] [a, b, c]
[c] [b, c]
[d]
Take the previous row and append:
a, b, c, d
=================================
[a] [a, b]
[b] [a, c] [a, b, c]
[c] [b, c]
[d] [a, d]
[b, d]
[c, d]
Again, take the previous row and append (remember that: "besides the one that were added"):
a, b, c, d
=================================
[a] [a, b]
[b] [a, c] [a, b, c]
[c] [b, c] [a, b, d] <--- [a, b] + [d]
[d] [a, d] [a, c, d] <--- [a, c] + [d]
[b, d] [b, c, d] <--- [b, c] + [d]
[c, d]
And the last one:
a, b, c, d
=================================================
[a] [a, b]
[b] [a, c] [a, b, c]
[c] [b, c] [a, b, d]
[d] [a, d] [a, c, d] [a, b, c, d]
[b, d] [b, c, d]
[c, d]
If you understand what happens above, it's all about writing the code. The only change I am making is not add elements that I already know will fail the check against max
. And here it is, it looks complicated, but in reality it is fairly trivial (with some exception of some edge cases):
static List<String> partitions(List<Choice> all, int max) {
ArrayList<ArrayList<ArrayList<Choice>>> previousResult = new ArrayList<>();
ArrayList<ArrayList<ArrayList<Choice>>> currentResult = new ArrayList<>();
int i = 0;
for(;i<all.size();++i) {
// add the first element
Choice current = all.get(i);
if(currentResult.isEmpty()) {
if(less(List.of(current), max)) {
// looks complicated, but all it does is adds a single element in the first index
ArrayList<Choice> inner = new ArrayList<>();
inner.add(current);
ArrayList<ArrayList<Choice>> in = new ArrayList<>();
in.add(inner);
currentResult.add(in);
previousResult.add(in);
}
} else {
if(less(List.of(current), max)) {
ArrayList<Choice> element = new ArrayList<>();
element.add(current);
currentResult.get(0).add(element);
}
if(currentResult.size() > 1) {
for(int j=0;j<i-1;++j) {
if(j < previousResult.size()) {
ArrayList<ArrayList<Choice>> p = previousResult.get(j);
for(int d=0;d<=p.size()-1;++d){
ArrayList<Choice> copy = new ArrayList<>(p.get(d));
copy.add(all.get(i));
if(less(copy, max)){
currentResult.get(j).add(copy);
}
}
}
}
}
// add tail if possible
ArrayList<ArrayList<Choice>> tail = new ArrayList<>();
ArrayList<Choice> t = new ArrayList<>(all.subList(0, i + 1));
if(less(t, max)) {
tail.add(t);
currentResult.add(tail);
}
if(currentResult.size() == 1) {
ArrayList<Choice> l = currentResult.get(0).stream().flatMap(List::stream).collect(Collectors.toCollection(ArrayList::new));
if(less(l, max)) {
tail.add(l);
currentResult.add(tail);
}
}
// smart copy here
previousResult = copy(previousResult, currentResult);
}
}
return
currentResult.stream()
.flatMap(List::stream)
.map(list -> {
int sum = list.stream().mapToInt(Choice::getCost).sum();
List<String> l = list.stream().map(Choice::getKey).collect(Collectors.toList());
return new AbstractMap.SimpleEntry<>(sum, l);
})
.sorted(Map.Entry.<Integer, List<String>>comparingByKey().reversed())
.filter(x -> x.getKey() <= max)
.map(Map.Entry::getValue)
.findFirst()
.orElse(List.of());
}
private static ArrayList<ArrayList<ArrayList<Choice>>> copy(ArrayList<ArrayList<ArrayList<Choice>>> previousResult, ArrayList<ArrayList<ArrayList<Choice>>> currentResult) {
return currentResult.stream()
.map(x -> x.stream().map(y -> (ArrayList<Choice>)y.clone()).collect(Collectors.toCollection(ArrayList::new)))
.collect(Collectors.toCollection(ArrayList::new));
}
private static boolean less(List<Choice> in, int max) {
return in.stream().mapToInt(Choice::getCost).sum() <= max;
}