我有两张地图:
Map<A, Collection<B>> mapAB
Map<B, Collection<C>> mapBC
我想将它们转换为Map<A, Collection<C>> mapAC
,我想知道是否有一种平滑的方式来做lambdas和变换。在我的特定情况下,集合都是集合,但我想解决集合中的问题。
我有一个想法是首先将两张地图合并为Map<A, Map<B, Collection<C>>>
,然后将其展平,但我对任何方法都持开放态度。
数据注释:B
应仅出现在与A
相关联的值集合中,mapBC
也是如此(给定的C
仅映射到来自一个B
)。因此,应该只有一条从给定A
到给定C
的路径,尽管可能有A -> B
映射,其中没有B -> C
映射,并且可能是B -> C
映射,其中没有对应的A -> B
映射。这些孤儿根本不会出现在结果mapAC
。
为了便于比较,以下是针对同一问题的纯粹必要方法的示例:
Map<A, Collection<C>> mapAC = new HashMap<>();
for (Entry<A, Collection<B>> entry : mapAB.entrySet()) {
Collection<C> cs = new HashSet<>();
for (B b : entry.getValue()) {
Collection<C> origCs = mapBC.get(b);
if (origCs != null) {
cs.addAll(origCs);
}
}
if (!cs.isEmpty()) {
mapAC.put(entry.getKey(), cs);
}
}
答案 0 :(得分:3)
我不是forEach
方法的粉丝,这是非常必要的。更纯粹的方法可能是
mapAB.entrySet().stream()
.flatMap(
entryAB -> entryAB.getValue().stream().flatMap(
b -> mapBC.getOrDefault(b, Collections.<C>emptyList())
.stream().map(
c -> new AbstractMap.SimpleEntry<>(entryAB.getKey(), c))))
// we now have a Stream<Entry<A, C>>
.groupingBy(
Entry::getKey,
mapping(Entry::getValue, toList()));
......或者可能是
mapA.entrySet().stream()
.flatMap(
entryAB -> entryAB.getValue().stream().map(
b -> new AbstractMap.SimpleEntry<>(
entryAB.getKey(),
mapBC.getOrDefault(b, Collections.<C>emptyList()))))
// we now have a Stream<Entry<A, Collection<C>>>
.groupingBy(
Entry::getKey,
mapping(Entry::getValue,
reducing(
Collections.<C>emptyList(),
(cs1, cs2) -> {
List<C> merged = new ArrayList<>(cs1);
merged.addAll(cs2);
return merged;
})));
答案 1 :(得分:3)
如果第一张地图中的某些b不存在于第二张地图中,那么您没有说明您想要做什么,所以这可能不是您想要的。
mapAB.entrySet().stream()
.filter(e -> e.getValue().stream().anyMatch(mapBC::containsKey))
.collect(toMap(
Map.Entry::getKey,
e->e.getValue().stream()
.filter(mapBC::containsKey)
.map(mapBC::get)
.flatMap(Collection::stream)
.collect(toList())
));
答案 2 :(得分:1)
我的StreamEx库提供了一个EntryStream
类,它是Map.Entry
个对象的流,带有一些额外的便捷操作。这就是我使用我的库来解决这个问题的方法:
Map<A, Collection<C>> mapAC = EntryStream.of(mapAB)
.flatMapValues(Collection::stream) // flatten values: now elements are Entry<A, B>
.mapValues(mapBC::get) // map only values: now elements are Entry<A, Collection<C>>
.nonNullValues() // remove entries with null values
.flatMapValues(Collection::stream) // flatten values again: now we have Entry<A, C>
.groupingTo(HashSet::new); // group them to Map using HashSet as value collections
由于创建了更多的中间对象,因此@Misha提供的优秀解决方案可能效率较低,但我认为通过这种方式编写和理解起来更容易。
答案 3 :(得分:0)
这个怎么样:
Map<A, Collection<B>> mapAB = new HashMap<>();
Map<B, Collection<C>> mapBC = new HashMap<>();
Map<A, Collection<C>> mapAC = new HashMap<>();
mapAB.entrySet().stream().forEach(a -> {
Collection<C> cs = new HashSet<>();
a.getValue().stream().filter(b -> mapBC.containsKey(b)).forEach(b -> cs.addAll(mapBC.get(b)));
mapAC.put(a.getKey(), cs);
});
答案 4 :(得分:0)
Map<A, Collection<C>> mapC =
mapA.entrySet().stream().collect(Collectors.toMap(
entry -> entry.getKey(),
entry -> entry.getValue().stream().flatMap(b -> mapB.get(b).stream())
.collect(Collectors.toSet())));
随意用https://gist.github.com/f72125cd4678069af7af.git或toList()替换Collectors.toSet()
。
答案 5 :(得分:0)
我实际上没有反对命令式方法。因为你正在将它收集到内存中,所以实际上使用lambdas没有任何好处,除非它们导致更清晰的代码。这里的命令式方法很好:
Map<A, Collection<C>> mapAC = new HashMap<>();
for (A key : mapAB.keySet()) {
Collection<C> cs = new HashSet<>();
mapAC.put(key, cs);
for (B b : mapAP.get(key)) {
cs.addAll(mapBC.get(b)==null ? Collections.emptyList() : mapBC.get(b));
}
}
虽然我已将if语句作为三元运算符排列,但我认为使用for循环中的键看起来更清晰。