【发布时间】:2018-11-07 11:09:53
【问题描述】:
在 C# Linq 中,GroupBy 返回 IEnumerable 的 IGrouping 项目,而这些项目又是所选值类型的项目的 IEnumerable。这是一个例子:
var namesAndScores = new Dictionary<string, int>> {
["David"] = 90,
["Jane"] = 91,
["Bill"] = 90,
["Tina"] = 89)
};
var IEnumerable<IGrouping<int, string>> namesGroupedByScore =
namesAndScores
.GroupBy(
kvp => kvp.Value,
kvp => kvp.Key
);
// Result:
// 90 : { David, Bill }
// 91 : { Jane }
// 89 : { Tina }
具体来说,请注意每个IGrouping<int, string> 都是IEnumerable<string>,而不是List<string>。 (它还有一个.Key 属性。)
GroupBy 显然必须在发出单个分组之前完全枚举输入项,但是,由于它确实发出 IEnumerable<string> 而不是 List<string>,如果不这样做可能会带来性能优势t 枚举整个分组,例如你刚刚做了.First()。
除此之外:从技术上讲,我想 GroupBy 可以等到您枚举它以使用输入中的单个项目,然后发出单个 IGrouping,并且仅将输入的其余部分枚举为 @987654337 @ 被枚举,在它搜索当前组中的下一个项目时将其他组收集到其内部数据结构中,但我发现这是一个不太可能且有问题的实现,并期望 GroupBy 将在调用时完全枚举。
First() 的代码如下所示:
var oneStudentForEachNumericScore = namesGroupedByScore
.ToDictionary(
grouping => grouping.Key,
grouping => grouping.First() // does not fully enumerate the values
);
// Result:
// 90 : David -- Bill is missing and we don't care
// 91 : Jane
// 89 : Tina
现在在 Java Streams 中,要进行分组,您必须进行收集,并且不能只给 groupingBy 收集器提供第二个 lambda 来提取值。如果您想要一个与整个输入不同的值,则必须再次映射(但请注意,groupingBy 收集器可让您在一个步骤中创建多级组...的组)。以下是与上述 C# 代码等效的代码:
Map<Integer, List<String>> namesGroupedByScore = namesAndScores
.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(
Map.Entry::getKey,
Collectors.toList(),
)
));
这似乎不太理想。所以我的问题是:
- 有什么方法可以更简单地表达这一点,而不必使用
Collectors.mapping来获取组项作为值? - 为什么我们必须收集到一个完全枚举的类型?有没有办法模拟 C# 的
GroupBy的IEnumerable值类型并从Collectors.mapping()返回Map<Integer, Stream<String>>,或者这没有用,因为无论如何都必须完全枚举值项?或者我们是否可以编写自己的Collectors.groupingBy,它接受一个 lambda 作为第二个参数并为我们完成这项工作,使语法更接近 Linq 的GroupBy,并且至少具有更简洁的语法并可能稍微提高性能? - 作为一项理论练习,即使没有实际用处,是否可以编写我们自己的 Java 流收集器
toStream(),它返回一个Stream并且不会迭代其输入,除非它被枚举(迭代一个元素时间,推迟)?
【问题讨论】:
-
如果你真的想要一个
Map<Integer, Stream<String>>,那么你可以使用toMap收集器namesAndScores.entrySet().stream().collect(toMap(Map.Entry::getValue, e -> Stream.of(e.getKey()), Stream::concat));的这个重载。 -
@Aominè 请注意
Stream.concat的警告:“从重复连接构造流时要小心。访问深度串联流的元素可能会导致深度调用链,甚至StackOverflowError”。这不是一个理论问题。最后,这种方法在幕后仍然存在一种存储结构,但是对于更大的组来说扩展性很差(并且不支持多次遍历)。
标签: java java-stream grouping collectors