如何使用 Java 8 流映射到多个元素?
我有这样的课:
class MultiDataPoint {
private DateTime timestamp;
private Map<String, Number> keyToData;
}
我想为每个 MultiDataPoint 生成 ,
and i want to produce , for each MultiDataPoint
class DataSet {
public String key;
List<DataPoint> dataPoints;
}
class DataPoint{
DateTime timeStamp;
Number data;
}
当然,一个键"在多个 MultiDataPoints 中可以是相同的.
of course a 'key' can be the same across multiple MultiDataPoints.
那么给定一个 List<MultiDataPoint>
,我如何使用 Java 8 流转换为 List<DataSet>
?
So given a List<MultiDataPoint>
, how do I use Java 8 streams to convert to 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();
}
推荐答案
这是一个有趣的问题,因为它表明有很多不同的方法可以达到相同的结果.下面我展示了三种不同的实现.
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.
集合框架中的默认方法:Java 8 向集合类添加了一些方法,这些方法与 Stream API 没有直接关系.使用这些方法,可以显着简化非流实现的实现:
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<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();
}
<小时>
具有扁平化和中间数据结构的流 API: 以下实现与 Stuart Marks 提供的解决方案几乎相同.与他的解决方案相比,下面的实现使用了一个匿名内部类作为中间数据结构.
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 class as intermediate data structure.
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:您也可以为每个MultiDataPoint创建一个Map,而不是扁平化原始数据结构,然后使用 reduce 操作将所有映射合并为一个映射.代码比上面的方案简单一点:
Stream API with map merging: Instead of flattening the original data structures, you can also create a Map for 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:
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());
}
您可以在 Collectors 类中找到 map 合并 的实现.不幸的是,从外部访问它有点棘手.以下是地图合并的另一种实现:
You can find an implementation of the map merger within the Collectors class. Unfortunately, it is a bit tricky to access it from the outside. Following is an alternative implementation of the map merger:
<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;
};
}
相关文章