Java 8 个流将一个列表 <Map<>> 按相同 <Key, Value> 分组到一个新列表<Map<>>
Java 8 stream grouping a List<Map<>> by the same <Key, Value> to a new List<Map<>>
我有一个List<Map<String,String>>
如:
Map<String, String> m1 = new HashMap<>();
m1.put("date", "2020.1.5");
m1.put("B", "10");
Map<String, String> m2 = new HashMap<>();
m2.put("date", "2020.1.5");
m2.put("A", "20");
Map<String, String> m3 = new HashMap<>();
m3.put("date", "2020.1.6");
m3.put("A", "30");
Map<String, String> m4 = new HashMap<>();
m4.put("date", "2020.1.7");
m4.put("C", "30");
List<Map<String, String>> before = new ArrayList<>();
before.add(m1);
before.add(m2);
before.add(m3);
before.add(m4);
我期望的结果是生成一个新的List map,按日期分组,所有在同一日期集合的条目会放在一起,如:
[{"A":"20","B":"10","date":"2020.1.5"},{"A":"30","date":"2020.1.6"},{"C":"30","date":"2020.1.7"}]
我尝试了以下方法,但始终不是我期望的结果。
stream().flatmap().collect(Collectors.groupingBy())
对这个问题的一些补充意见:
我用 for LOOP 解决了这个问题,但是当列表大小约为 50000 时应用程序挂起,所以我寻求一种性能更好的方法来执行此操作。 Java 据我所知,8 流平面图可能是一种方式。
所以关键点不仅在于重新映射,还在于使用最高效的方式来做到这一点。
before
.stream()
.collect(Collectors.toMap((m) -> m.get("date"), m -> m, (a,b) -> {
Map<String, String> res = new HashMap<>();
res.putAll(a);
res.putAll(b);
return res;
}))
.values();
这就是您正在寻找的解决方案。
toMap
函数接收3个参数:
- 键映射器,在你的例子中是日期
- 值映射器,即正在处理的映射本身
- 合并功能,将 2 个具有相同日期的地图并将所有键放在一起
输出:
[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]
您可以按顺序使用一定数量的收集器获得完全相同的结果:
Collectors.groupingBy
按日期分组
Collectors.reducing
合并 Map<String, String>
项
Collectors.collectingAndThen
将值从 Map<String, Optional<Map<String, String>>>
转换为先前减少到最终输出 List<Map<String, String>>
. 的结果
List<Map<String, String>> list = before.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
m -> m.get("date"),
Collectors.reducing((l, r) -> {
l.putAll(r);
return l; })
),
o -> o.values().stream()
.flatMap(Optional::stream)
.collect(Collectors.toList())));
list
包含您要查找的内容:
[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]
重要:这个解决方案有两个缺点:
- 它看起来很笨拙,独立观众可能看不清楚
- 它变异(修改)了
List<Map<String, String>> before
中包含的原始地图。
可以这样操作:
List<Map<String, String>> remapped = before.stream()
.collect(Collectors.groupingBy(m -> m.get("date")))
.values().stream()
.map(e -> e.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x1, x2) -> x1)))
.collect(Collectors.toList());
remapped.forEach(System.out::println);
输出:
{date=2020.1.5, A=20, B=10}
{date=2020.1.6, A=30}
{date=2020.1.7, C=30}
您可以使用 groupingBy
和 Collector.of
List<Map<String, String>> list = new ArrayList<>(before.stream()
.collect(Collectors.groupingBy(
k -> k.get("date"),
Collector.of( HashMap<String,String>::new,
(m,e)-> m.putAll(e),
(map1,map2)->{ map1.putAll(map2); return map1;}
))).values());
这里先用Collectors.groupingBy
按日期分组。然后使用 Collector.of
定义自定义收集器,将 List<Map<String, String>>
收集到 Map<String, String>
。使用地图值创建列表后。
并使用 Java 9
中的 Collectors.flatMapping
List<Map<String, String>> list = new ArrayList<>(before.stream()
.collect(Collectors.groupingBy(
k -> k.get("date"),
Collectors.flatMapping(m -> m.entrySet().stream(),
Collectors.toMap(k -> k.getKey(), v -> v.getValue(), (a,b) -> a))))
.values());
我有一个List<Map<String,String>>
如:
Map<String, String> m1 = new HashMap<>();
m1.put("date", "2020.1.5");
m1.put("B", "10");
Map<String, String> m2 = new HashMap<>();
m2.put("date", "2020.1.5");
m2.put("A", "20");
Map<String, String> m3 = new HashMap<>();
m3.put("date", "2020.1.6");
m3.put("A", "30");
Map<String, String> m4 = new HashMap<>();
m4.put("date", "2020.1.7");
m4.put("C", "30");
List<Map<String, String>> before = new ArrayList<>();
before.add(m1);
before.add(m2);
before.add(m3);
before.add(m4);
我期望的结果是生成一个新的List map,按日期分组,所有在同一日期集合的条目会放在一起,如:
[{"A":"20","B":"10","date":"2020.1.5"},{"A":"30","date":"2020.1.6"},{"C":"30","date":"2020.1.7"}]
我尝试了以下方法,但始终不是我期望的结果。
stream().flatmap().collect(Collectors.groupingBy())
对这个问题的一些补充意见:
我用 for LOOP 解决了这个问题,但是当列表大小约为 50000 时应用程序挂起,所以我寻求一种性能更好的方法来执行此操作。 Java 据我所知,8 流平面图可能是一种方式。 所以关键点不仅在于重新映射,还在于使用最高效的方式来做到这一点。
before
.stream()
.collect(Collectors.toMap((m) -> m.get("date"), m -> m, (a,b) -> {
Map<String, String> res = new HashMap<>();
res.putAll(a);
res.putAll(b);
return res;
}))
.values();
这就是您正在寻找的解决方案。
toMap
函数接收3个参数:
- 键映射器,在你的例子中是日期
- 值映射器,即正在处理的映射本身
- 合并功能,将 2 个具有相同日期的地图并将所有键放在一起
输出:
[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]
您可以按顺序使用一定数量的收集器获得完全相同的结果:
Collectors.groupingBy
按日期分组Collectors.reducing
合并Map<String, String>
项Collectors.collectingAndThen
将值从Map<String, Optional<Map<String, String>>>
转换为先前减少到最终输出List<Map<String, String>>
. 的结果
List<Map<String, String>> list = before.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
m -> m.get("date"),
Collectors.reducing((l, r) -> {
l.putAll(r);
return l; })
),
o -> o.values().stream()
.flatMap(Optional::stream)
.collect(Collectors.toList())));
list
包含您要查找的内容:
[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]
重要:这个解决方案有两个缺点:
- 它看起来很笨拙,独立观众可能看不清楚
- 它变异(修改)了
List<Map<String, String>> before
中包含的原始地图。
可以这样操作:
List<Map<String, String>> remapped = before.stream()
.collect(Collectors.groupingBy(m -> m.get("date")))
.values().stream()
.map(e -> e.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x1, x2) -> x1)))
.collect(Collectors.toList());
remapped.forEach(System.out::println);
输出:
{date=2020.1.5, A=20, B=10}
{date=2020.1.6, A=30}
{date=2020.1.7, C=30}
您可以使用 groupingBy
和 Collector.of
List<Map<String, String>> list = new ArrayList<>(before.stream()
.collect(Collectors.groupingBy(
k -> k.get("date"),
Collector.of( HashMap<String,String>::new,
(m,e)-> m.putAll(e),
(map1,map2)->{ map1.putAll(map2); return map1;}
))).values());
这里先用Collectors.groupingBy
按日期分组。然后使用 Collector.of
定义自定义收集器,将 List<Map<String, String>>
收集到 Map<String, String>
。使用地图值创建列表后。
并使用 Java 9
中的Collectors.flatMapping
List<Map<String, String>> list = new ArrayList<>(before.stream()
.collect(Collectors.groupingBy(
k -> k.get("date"),
Collectors.flatMapping(m -> m.entrySet().stream(),
Collectors.toMap(k -> k.getKey(), v -> v.getValue(), (a,b) -> a))))
.values());