![](/img/trans.png)
[英]How to merge a nested List into a simple List using Java 8 Stream API
[英]Merge list of maps using Java 8 Stream API
我正在嘗試學習Java 8 Stream,當我嘗試將某些函數轉換為java8進行練習時。 我遇到一個問題。
我很好奇如何將跟隨代碼轉換為Java流格式。
/*
* input example:
* [
{
"k1": { "kk1": 1, "kk2": 2},
"k2": {"kk1": 3, "kk2": 4}
}
{
"k1": { "kk1": 10, "kk2": 20},
"k2": {"kk1": 30, "kk2": 40}
}
]
* output:
* {
"k1": { "kk1": 11, "kk2": 22},
"k2": {"kk1": 33, "kk2": 44}
}
*
*
*/
private static Map<String, Map<String, Long>> mergeMapsValue(List<Map<String, Map<String, Long>>> valueList) {
Set<String> keys_1 = valueList.get(0).keySet();
Set<String> keys_2 = valueList.get(0).entrySet().iterator().next().getValue().keySet();
Map<String, Map<String, Long>> result = new HashMap<>();
for (String k1: keys_1) {
result.put(k1, new HashMap<>());
for (String k2: keys_2) {
long total = 0;
for (Map<String, Map<String, Long>> mmap: valueList) {
Map<String, Long> m = mmap.get(k1);
if (m != null && m.get(k2) != null) {
total += m.get(k2);
}
}
result.get(k1).put(k2, total);
}
}
return result;
}
這里的技巧是正確收集內部地圖。 工作流程為:
List<Map<String, Map<String, Long>>>
的列表平面映射到地圖項Stream<Map.Entry<String, Map<String, Long>>>
。 通過合並來收集地圖將理想地保證一個flatMapping
收集器,盡管Java 9中將存在它 ,但不幸的是Java 8中不存在該flatMapping
器(請參閱JDK-8071600 )。 對於Java 8,可以使用由所提供的一個StreamEx庫(和使用MoreCollectors.flatMapping
在下面的代碼)。
private static Map<String, Map<String, Long>> mergeMapsValue(List<Map<String, Map<String, Long>>> valueList) {
return valueList.stream()
.flatMap(e -> e.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.flatMapping(
e -> e.getValue().entrySet().stream(),
Collectors.<Map.Entry<String,Long>,String,Long>toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum)
)
));
}
在不使用此方便的收集器的情況下,我們仍然可以使用等效的語義構建自己的收集器:
private static Map<String, Map<String, Long>> mergeMapsValue2(List<Map<String, Map<String, Long>>> valueList) {
return valueList.stream()
.flatMap(e -> e.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collector.of(
HashMap::new,
(r, t) -> t.getValue().forEach((k, v) -> r.merge(k, v, Long::sum)),
(r1, r2) -> { r2.forEach((k, v) -> r1.merge(k, v, Long::sum)); return r1; }
)
));
}
首先,轉換為使用computeIfAbsent
並merge
將為我們提供以下內容:
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
final Map<K1, Map<K2, Long>> result = new HashMap<>();
for (final Map<K1, Map<K2, Long>> map : valueList) {
for (final Map.Entry<K1, Map<K2, Long>> sub : map.entrySet()) {
for (final Map.Entry<K2, Long> subsub : sub.getValue().entrySet()) {
result.computeIfAbsent(sub.getKey(), k1 -> new HashMap<>())
.merge(subsub.getKey(), subsub.getValue(), Long::sum);
}
}
}
return result;
}
這從內部循環中消除了很多邏輯。
下面的代碼是錯誤的 ,這里留給我參考。
轉換為Stream
API不會使它更整潔,但可以嘗試一下。
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
return valueList.stream()
.flatMap(v -> v.entrySet().stream())
.collect(groupingBy(Entry::getKey, collectingAndThen(mapping(Entry::getValue, toList()), l -> l.stream()
.reduce(new HashMap<>(), (l2, r2) -> {
r2.forEach((k, v) -> l2.merge(k, v, Long::sum);
return l2;
}))));
}
這是我設法解決的-這太可怕了。 問題在於,使用foreach
方法時,您可以引用迭代的每個級別-這使邏輯變得簡單。 使用功能方法時,您需要分別考慮每個折疊操作。
它是如何工作的?
我們首先stream()
我們的List<Map<K1, Map<K2, Long>>>
,得到Stream<Map<K1, Map<K2, Long>>>
。 接下來,我們對每個元素進行flatMap
,並給出Stream<Entry<K1, Map<K2, Long>>>
-因此,我們將第一個維度展平。 但是我們無法進一步展平,因為我們需要K1
值。
因此,我們然后在K1
值上使用collect(groupingBy)
,為我們提供Map<K1, SOMETHING>
-什么是東西?
好吧,首先我們使用一個mapping(Entry::getValue, toList())
給我們一個Map<K1, List<Map<K2, Long>>>
。 然后,我們使用collectingAndThen
來獲取List<Map<K2, Long>>
並將其減少。 請注意,這意味着我們生成了一個中間List
,這很浪費-您可以通過使用自定義Collector
來解決此問題。
為此,我們使用List.stream().reduce(a, b)
,其中a
是初始值, b
是“ fold”操作。 a
設置為new HashMap<>()
, b
a
兩個值:函數的上一個應用程序的初始值或結果以及List
的當前項。 因此,對於List
每個項目,請使用Map.merge
組合值。
我會說這種方法或多或少難以辨認-您將無法在幾個小時的時間內解密,更不用說幾天了。
我從Tunaki取得了flatMap(e -> e.entrySet().stream())
部分,但對收集器使用了一個較短的變體:
Map<String, Integer> merged = maps.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
更詳細的例子:
Map<String, Integer> a = new HashMap<String, Integer>() {{
put("a", 2);
put("b", 5);
}};
Map<String, Integer> b = new HashMap<String, Integer>() {{
put("a", 7);
}};
List<Map<String, Integer>> maps = Arrays.asList(a, b);
Map<String, Integer> merged = maps.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
assert merged.get("a") == 9;
assert merged.get("b") == 5;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.