I have a problem with correctly combining multiple Collectors::groupingBy
functions and then applying them all at once to a given input.
Let's say I have some class implementing following interface:
interface Something {
String get1();
String get2();
String get3();
String get4();
}
And now I can have some list of combinations of the methods from this interface, ie these lists can be: [Something::get1, Something::get3]
, [Something::get2, Something::get1, Something::get3]
.
Now, having such a list of methods and having a list of somethings, I would like to group those somethings by getters.
What I mean is that for example for the list [Something::get1, Something::get3]
and a list [Something1, Something2, ...]
I want to get the list of somethings grouped firstly by get1
and then by get2
.
This can be achieved this way:
var lst = List.of(smth1, smth2, smth3);
lst.stream()
.collect(Collectors.groupingBy(Something::get1, Collectors.groupingBy(Something::get3)))
What if I have any arbitrary list of methods that I would like to apply to grouping?
I was thinking of something like this (ofc. this does not work, but you will get the idea):
Assume that List<Function<Something, String>> groupingFunctions
is our list of methods we want to apply to grouping.
var collector = groupingFunctions.stream()
.reduce((f1, f2) -> Collectors.groupingBy(f1, Collectors.groupingBy(f2)))
and then
List.of(smth1, smth2, smth3).stream().collect(collector)
But this approach does not work. How to achieve the result I am thinking of?
You can do this:
public static Collector createCollector(Function<A, String>... groupKeys) {
Collector collector = Collectors.toList();
for (int i = groupKeys.length - 1; i >= 0; i--) {
collector = Collectors.groupingBy(groupKeys[i], collector);
}
return collector;
}
This give you a raw collector, hence your stream result after grouping is also raw.
Collector collector = createCollector(Something::get1, Something::get2);
You can use this collector
like this:
Object result = somethingList.stream().collect(collector);
Because you know how many groupingBy
you passed to the collector, you can cast it to appropriate Map
result. In this case two groupingBy
is applied:
Map<String, Map<String, List<Something>>> mapResult = (Map<String, Map<String, List<Something>>>) result
Since you don't know how many functions are in the list, you can't declare a compile-time type reflecting the nesting. But even when using a collector type producing some unknown result type, composing it is not solvable in the clean functional way you intend. The closest you can get is
var collector = groupingFunctions.stream()
.<Collector<Something,?,?>>reduce(
Collectors.toList(),
(c,f) -> Collectors.groupingBy(f, c),
(c1,c2) -> { throw new UnsupportedOperationException("can't handle that"); });
which has two fundamental problems. There is no way to provide a valid merge function for two Collector
instances, so while this may work with a sequential operation, it is not a clean solution. Further, the nesting of the result maps will be in the opposite order; the last function of the list will provide the keys to the outermost map.
There might be ways to fix that, but all of them will make the code even more complicated. Compare this with a straight-forward loop:
Collector<Something,?,?> collector = Collectors.toList();
for(var i = groupingFunctions.listIterator(groupingFunctions.size()); i.hasPrevious(); )
collector = Collectors.groupingBy(i.previous(), collector);
You can use the collector like
Object o = lst.stream().collect(collector);
but need instanceof
and type casts to process the Map
s…
It would be cleaner to create a single, non-nested Map
with List
keys which reflect the grouping functions:
Map<List<String>,List<Something>> map = lst.stream().collect(Collectors.groupingBy(
o -> groupingFunctions.stream().map(f -> f.apply(o))
.collect(Collectors.toUnmodifiableList())));
It would allow querying entries like map.get(List.of(arguments, matching, grouping, functions))
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.