java - Combine multiple `Collectors::groupBy` functions with Java Streams
问题描述
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, i.e. 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?
解决方案
由于您不知道列表中有多少个函数,因此您不能声明反映嵌套的编译时类型。但是,即使使用产生某些未知结果类型的收集器类型,也无法以您想要的干净功能方式来组合它。你能得到的最接近的是
var collector = groupingFunctions.stream()
.<Collector<Something,?,?>>reduce(
Collectors.toList(),
(c,f) -> Collectors.groupingBy(f, c),
(c1,c2) -> { throw new UnsupportedOperationException("can't handle that"); });
这有两个基本问题。无法为两个Collector
实例提供有效的合并功能,因此虽然这可能适用于顺序操作,但它不是一个干净的解决方案。此外,结果映射的嵌套顺序相反;列表的最后一个函数将提供最外层地图的键。
可能有办法解决这个问题,但所有这些都会使代码更加复杂。将此与直接循环进行比较:
Collector<Something,?,?> collector = Collectors.toList();
for(var i = groupingFunctions.listIterator(groupingFunctions.size()); i.hasPrevious(); )
collector = Collectors.groupingBy(i.previous(), collector);
你可以像这样使用收集器
Object o = lst.stream().collect(collector);
但需要instanceof
和类型转换来处理Map
s…</p>
Map
使用List
反映分组功能的键创建单个非嵌套键会更简洁:
Map<List<String>,List<Something>> map = lst.stream().collect(Collectors.groupingBy(
o -> groupingFunctions.stream().map(f -> f.apply(o))
.collect(Collectors.toUnmodifiableList())));
它将允许查询条目,例如map.get(List.of(arguments, matching, grouping, functions))
推荐阅读
- c# - System.ObjectDisposedException:无法访问已处置的对象。- 如何检查应用程序是否在 Xamarin 中运行?
- visual-c++ - 在 NVRAM 中保留自定义数据
- nlg - 自然语言生成 (NLG) 来描述财务资产负债表
- php - 如何从SMARTY中的变量中删除第一个单词
- mysql - 订购事件数据
- jupyterhub - 在所有 JupyterHub 笔记本上显示维护消息
- php - 从 foreach php 中删除重复项
- python - 散点图和缺失值
- java - 我想在清除我的应用数据后打开一个新活动
- c# - 如何在不创建新客户端或破坏现有客户端的情况下制作只有 HTTP 和 HTTPS 的 WCF 服务