如何使用 Java 8 流映射到多个元素?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23620360/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to map to multiple elements with Java 8 streams?
提问by pdeva
I have a class like this:
我有一个这样的课程:
class MultiDataPoint {
private DateTime timestamp;
private Map<String, Number> keyToData;
}
and i want to produce , for each MultiDataPoint
我想为每个 MultiDataPoint 生成
class DataSet {
public String key;
List<DataPoint> dataPoints;
}
class DataPoint{
DateTime timeStamp;
Number data;
}
of course a 'key' can be the same across multiple MultiDataPoints.
当然,多个 MultiDataPoint 中的“键”可以相同。
So given a List<MultiDataPoint>
, how do I use Java 8 streams to convert to List<DataSet>
?
那么给定一个List<MultiDataPoint>
,我如何使用 Java 8 流转换为List<DataSet>
?
This is how I am currently doing the conversion without streams:
这就是我目前在没有流的情况下进行转换的方式:
Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints)
{
Map<String, DataSet> setMap = new HashMap<>();
multiDataPoints.forEach(pt -> {
Map<String, Number> data = pt.getData();
data.entrySet().forEach(e -> {
String seriesKey = e.getKey();
DataSet dataSet = setMap.get(seriesKey);
if (dataSet == null)
{
dataSet = new DataSet(seriesKey);
setMap.put(seriesKey, dataSet);
}
dataSet.dataPoints.add(new DataPoint(pt.getTimestamp(), e.getValue()));
});
});
return setMap.values();
}
采纳答案by nosid
It's an interesting question, because it shows that there are a lot of different approaches to achieve the same result. Below I show three different implementations.
这是一个有趣的问题,因为它表明有很多不同的方法可以实现相同的结果。下面我展示了三种不同的实现。
Default methods in Collection Framework:Java 8 added some methods to the collections classes, that are not directly related to the Stream API. Using these methods, you can significantly simplify the implementation of the non-stream implementation:
Collection Framework 中的默认方法:Java 8 向集合类添加了一些与Stream API没有直接关系的方法。使用这些方法,您可以显着简化非流实现的实现:
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
Map<String, DataSet> result = new HashMap<>();
multiDataPoints.forEach(pt ->
pt.keyToData.forEach((key, value) ->
result.computeIfAbsent(
key, k -> new DataSet(k, new ArrayList<>()))
.dataPoints.add(new DataPoint(pt.timestamp, value))));
return result.values();
}
Stream API with flatten and intermediate data structure:The following implementation is almost identical to the solution provided by Stuart Marks. In contrast to his solution, the following implementation uses an anonymous inner classas intermediate data structure.
具有扁平化和中间数据结构的 Stream API:以下实现与 Stuart Marks 提供的解决方案几乎相同。与他的解决方案相反,以下实现使用匿名内部类作为中间数据结构。
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.flatMap(mdp -> mdp.keyToData.entrySet().stream().map(e ->
new Object() {
String key = e.getKey();
DataPoint dataPoint = new DataPoint(mdp.timestamp, e.getValue());
}))
.collect(
collectingAndThen(
groupingBy(t -> t.key, mapping(t -> t.dataPoint, toList())),
m -> m.entrySet().stream().map(e -> new DataSet(e.getKey(), e.getValue())).collect(toList())));
}
Stream API with map merging:Instead of flattening the original data structures, you can also create a Mapfor each MultiDataPoint, and then merge all maps into a single map with a reduce operation. The code is a bit simpler than the above solution:
Stream API with map merging:你还可以为每个MultiDataPoint创建一个Map,而不是扁平化原始数据结构,然后使用 reduce 操作将所有 map 合并为一个 map。代码比上面的解决方案简单一点:
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.map(mdp -> mdp.keyToData.entrySet().stream()
.collect(toMap(e -> e.getKey(), e -> asList(new DataPoint(mdp.timestamp, e.getValue())))))
.reduce(new HashMap<>(), mapMerger())
.entrySet().stream()
.map(e -> new DataSet(e.getKey(), e.getValue()))
.collect(toList());
}
You can find an implementation of the map mergerwithin the Collectorsclass. Unfortunately, it is a bit tricky to access it from the outside. Following is an alternative implementation of the map merger:
您可以在Collectors类中找到地图合并的实现。不幸的是,从外部访问它有点棘手。以下是地图合并的另一种实现:
<K, V> BinaryOperator<Map<K, List<V>>> mapMerger() {
return (lhs, rhs) -> {
Map<K, List<V>> result = new HashMap<>();
lhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
rhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
return result;
};
}
回答by Stuart Marks
To do this, I had to come up with an intermediate data structure:
为此,我必须想出一个中间数据结构:
class KeyDataPoint {
String key;
DateTime timestamp;
Number data;
// obvious constructor and getters
}
With this in place, the approach is to "flatten" each MultiDataPoint into a list of (timestamp, key, data) triples and stream together all such triples from the list of MultiDataPoint.
有了这个,方法是将每个 MultiDataPoint “扁平化”成一个(时间戳、密钥、数据)三元组的列表,并将来自 MultiDataPoint 列表的所有这些三元组流式传输在一起。
Then, we apply a groupingBy
operation on the string key in order to gather the data for each key together. Note that a simple groupingBy
would result in a map from each string key to a list of the corresponding KeyDataPoint triples. We don't want the triples; we want DataPoint instances, which are (timestamp, data) pairs. To do this we apply a "downstream" collector of the groupingBy
which is a mapping
operation that constructs a new DataPoint by getting the right values from the KeyDataPoint triple. The downstream collector of the mapping
operation is simply toList
which collects the DataPoint objects of the same group into a list.
然后,我们groupingBy
对字符串键应用操作,以便将每个键的数据收集在一起。请注意,简单groupingBy
将导致从每个字符串键到相应 KeyDataPoint 三元组列表的映射。我们不想要三元组;我们想要 DataPoint 实例,它们是 (timestamp, data) 对。为此,我们应用了一个“下游”收集器,该收集器groupingBy
是一种mapping
通过从 KeyDataPoint 三元组中获取正确值来构造新 DataPoint的操作。操作的下游收集器mapping
只是toList
将同一组的 DataPoint 对象收集到一个列表中。
Now we have a Map<String, List<DataPoint>>
and we want to convert it to a collection of DataSet objects. We simply stream out the map entries and construct DataSet objects, collect them into a list, and return it.
现在我们有一个Map<String, List<DataPoint>>
,我们想把它转换成一个 DataSet 对象的集合。我们简单地输出映射条目并构造 DataSet 对象,将它们收集到一个列表中,然后返回它。
The code ends up looking like this:
代码最终看起来像这样:
Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.flatMap(mdp -> mdp.getData().entrySet().stream()
.map(e -> new KeyDataPoint(e.getKey(), mdp.getTimestamp(), e.getValue())))
.collect(groupingBy(KeyDataPoint::getKey,
mapping(kdp -> new DataPoint(kdp.getTimestamp(), kdp.getData()), toList())))
.entrySet().stream()
.map(e -> new DataSet(e.getKey(), e.getValue()))
.collect(toList());
}
I took some liberties with constructors and getters, but I think they should be obvious.
我对构造函数和 getter 有一些自由,但我认为它们应该是显而易见的。