如何使用 Java 流 API 将地图列表与列表值合并?
How to merge lists of Map with Lists values using Java Streams API?
如何通过 X.p 减少 Map<X, List<String>>
分组并同时加入所有列表值,以便最后有 Map<Integer, List<String>>
?
这是我目前尝试过的方法:
class X {
int p;
int q;
public X(int p, int q) { this.p = p; this.q = q; }
}
Map<X, List<String>> x = new HashMap<>();
x.put(new X(123,5), Arrays.asList("A","B"));
x.put(new X(123,6), Arrays.asList("C","D"));
x.put(new X(124,7), Arrays.asList("E","F"));
Map<Integer, List<String>> z = x.entrySet().stream().collect(Collectors.groupingBy(
entry -> entry.getKey().p,
mapping(Map.Entry::getValue,
reducing(new ArrayList<>(), (a, b) -> { a.addAll(b); return a; }))));
System.out.println("z="+z);
但结果是:z={123=[E, F, A, B, C, D], 124=[E, F, A, B, C, D]}.
我想要 z={123=[A, B, C, D], 124=[E, F]}
这是使用两个 Stream 管道执行此操作的一种方法:
Map<Integer, List<String>> z =
// first process the entries of the original Map and produce a
// Map<Integer,List<List<String>>>
x.entrySet()
.stream()
.collect(Collectors.groupingBy(entry -> entry.getKey().p,
mapping(Map.Entry::getValue,
toList())))
// then process the entries of the intermediate Map and produce a
// Map<Integer,List<String>>
.entrySet()
.stream()
.collect (toMap (Map.Entry::getKey,
e -> e.getValue()
.stream()
.flatMap(List::stream)
.collect(toList())));
Java 9 应该添加一个 flatMapping 收集器,这将使您的生活更轻松(感谢 Holger,我了解了这个新功能)。
输出:
z={123=[A, B, C, D], 124=[E, F]}
有一种方法可以通过编写自己的收集器 运行 来实现这一点:
Map<Integer, List<String>> z = x.entrySet().stream().collect(
Collectors.groupingBy(entry -> entry.getKey().p,
Collectors.mapping(Entry::getValue,
Collector.of(ArrayList::new, (a, b) -> a.addAll(b), (a, b) -> {
a.addAll(b);
return a;
})
)
)
);
使用 EntryStream
class of my StreamEx 库可以很容易地解决这些任务:
Map<Integer, List<String>> z = EntryStream.of(x)
.mapKeys(k -> k.p)
.flatMapValues(List::stream)
.grouping();
在内部它被转换成这样:
Map<Integer, List<String>> z = x.entrySet().stream()
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
.<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
.map(s -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), s)))
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.mapping(e -> e.getValue(), Collectors.toList())));
所以它实际上是一个单流管道。
如果不想使用第三方代码,可以把上面的版本稍微简化一下:
Map<Integer, List<String>> z = x.entrySet().stream()
.<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
.map(s -> new AbstractMap.SimpleEntry<>(e.getKey().p, s)))
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.mapping(e -> e.getValue(), Collectors.toList())));
虽然还是很难看
最后请注意,在 JDK9 中有一个名为 flatMapping
的新标准收集器,可以通过以下方式实现:
public static <T, U, A, R>
Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
Collector<? super U, A, R> downstream) {
BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
return Collector.of(downstream.supplier(),
(r, t) -> {
try (Stream<? extends U> result = mapper.apply(t)) {
if (result != null)
result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
}
},
downstream.combiner(), downstream.finisher(),
downstream.characteristics().toArray(new Collector.Characteristics[0]));
}
使用这个收集器,您的任务可以更简单地解决,无需额外的库:
Map<Integer, List<String>> z = x.entrySet().stream()
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
.collect(Collectors.groupingBy(e -> e.getKey(),
flatMapping(e -> e.getValue().stream(), Collectors.toList())));
您未正确使用 reducing
收集器。第一个参数必须是缩减操作的 身份值 。但是您通过向其添加值来修改它,这完美地解释了结果:所有值都添加到相同的 ArrayList
,这应该是不变的标识值。
你要做的是 Mutable reduction and Collectors.reducing
is not appropriate for that. You may create an appropriate collector using the method Collector.of(…)
:
Map<Integer, List<String>> z = x.entrySet().stream().collect(groupingBy(
entry -> entry.getKey().p, Collector.of(
ArrayList::new, (l,e)->l.addAll(e.getValue()), (a,b)->{a.addAll(b);return a;})));
如何通过 X.p 减少 Map<X, List<String>>
分组并同时加入所有列表值,以便最后有 Map<Integer, List<String>>
?
这是我目前尝试过的方法:
class X {
int p;
int q;
public X(int p, int q) { this.p = p; this.q = q; }
}
Map<X, List<String>> x = new HashMap<>();
x.put(new X(123,5), Arrays.asList("A","B"));
x.put(new X(123,6), Arrays.asList("C","D"));
x.put(new X(124,7), Arrays.asList("E","F"));
Map<Integer, List<String>> z = x.entrySet().stream().collect(Collectors.groupingBy(
entry -> entry.getKey().p,
mapping(Map.Entry::getValue,
reducing(new ArrayList<>(), (a, b) -> { a.addAll(b); return a; }))));
System.out.println("z="+z);
但结果是:z={123=[E, F, A, B, C, D], 124=[E, F, A, B, C, D]}.
我想要 z={123=[A, B, C, D], 124=[E, F]}
这是使用两个 Stream 管道执行此操作的一种方法:
Map<Integer, List<String>> z =
// first process the entries of the original Map and produce a
// Map<Integer,List<List<String>>>
x.entrySet()
.stream()
.collect(Collectors.groupingBy(entry -> entry.getKey().p,
mapping(Map.Entry::getValue,
toList())))
// then process the entries of the intermediate Map and produce a
// Map<Integer,List<String>>
.entrySet()
.stream()
.collect (toMap (Map.Entry::getKey,
e -> e.getValue()
.stream()
.flatMap(List::stream)
.collect(toList())));
Java 9 应该添加一个 flatMapping 收集器,这将使您的生活更轻松(感谢 Holger,我了解了这个新功能)。
输出:
z={123=[A, B, C, D], 124=[E, F]}
有一种方法可以通过编写自己的收集器 运行 来实现这一点:
Map<Integer, List<String>> z = x.entrySet().stream().collect(
Collectors.groupingBy(entry -> entry.getKey().p,
Collectors.mapping(Entry::getValue,
Collector.of(ArrayList::new, (a, b) -> a.addAll(b), (a, b) -> {
a.addAll(b);
return a;
})
)
)
);
使用 EntryStream
class of my StreamEx 库可以很容易地解决这些任务:
Map<Integer, List<String>> z = EntryStream.of(x)
.mapKeys(k -> k.p)
.flatMapValues(List::stream)
.grouping();
在内部它被转换成这样:
Map<Integer, List<String>> z = x.entrySet().stream()
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
.<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
.map(s -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), s)))
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.mapping(e -> e.getValue(), Collectors.toList())));
所以它实际上是一个单流管道。
如果不想使用第三方代码,可以把上面的版本稍微简化一下:
Map<Integer, List<String>> z = x.entrySet().stream()
.<Entry<Integer, String>>flatMap(e -> e.getValue().stream()
.map(s -> new AbstractMap.SimpleEntry<>(e.getKey().p, s)))
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.mapping(e -> e.getValue(), Collectors.toList())));
虽然还是很难看
最后请注意,在 JDK9 中有一个名为 flatMapping
的新标准收集器,可以通过以下方式实现:
public static <T, U, A, R>
Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
Collector<? super U, A, R> downstream) {
BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
return Collector.of(downstream.supplier(),
(r, t) -> {
try (Stream<? extends U> result = mapper.apply(t)) {
if (result != null)
result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
}
},
downstream.combiner(), downstream.finisher(),
downstream.characteristics().toArray(new Collector.Characteristics[0]));
}
使用这个收集器,您的任务可以更简单地解决,无需额外的库:
Map<Integer, List<String>> z = x.entrySet().stream()
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey().p, e.getValue()))
.collect(Collectors.groupingBy(e -> e.getKey(),
flatMapping(e -> e.getValue().stream(), Collectors.toList())));
您未正确使用 reducing
收集器。第一个参数必须是缩减操作的 身份值 。但是您通过向其添加值来修改它,这完美地解释了结果:所有值都添加到相同的 ArrayList
,这应该是不变的标识值。
你要做的是 Mutable reduction and Collectors.reducing
is not appropriate for that. You may create an appropriate collector using the method Collector.of(…)
:
Map<Integer, List<String>> z = x.entrySet().stream().collect(groupingBy(
entry -> entry.getKey().p, Collector.of(
ArrayList::new, (l,e)->l.addAll(e.getValue()), (a,b)->{a.addAll(b);return a;})));