我遇到了一个让我疯狂的问题。我试图避免为这个地图反演创建一个中间对象。 (对目标的肯定:我有一个嵌套数据结构的地图,我想反转和爆炸。所以,
Map<Foo,Set<String>> fooStringMap
变为
Map<String,Foo> expandedStringFooMap
//Inverting a map is simple
private <X,Y> Map<Y,X> invertMap(Map<X,Y> source){
return source.entrySet().stream()
.collect(Collectors.toMap(Entry::getValue,Entry::getKey)
private <A,B> Map<A,B> explodeMapWithCollection(Map<? extends Collection<A>, B> collectionMap){
collectionMap.entrySet().stream()
.flatMap(x -> x.getKey().stream().collect(Collectors.toMap(Function.identity(),x.getValue())))
.collect(Collectors.toMap(Entry::getKey,Entry::getValue));
}
目前,这不起作用。我甚至不认为上面会编译,所以只考虑伪代码。
我用这样的一对解决了这个问题:
someMap.keySet().stream().flatMap(key->someMap.get(key).stream().map(val -> new
Pair<>(val,key))).collect(Collectors.toMap(Pair::getLeft,Pair::getRight)));
这就像一个魅力,但我(为了我自己的启发)喜欢避免创建中间对。我知道有必要这样做,但我似乎迷失在synatax中。
答案 0 :(得分:2)
以下是一种在条目集上使用自定义Stream#collect
的方法。有人可能会说,这不是“完全正常运转”。由于隐藏在累加器中的forEach
,但在某些时候,必须创建地图条目,并且我不确定是否存在&#34;优雅&#34;使用来自Set
的流(条目值)的方法,仍然可以访问条目密钥(它将成为新条目的值)。
旁注(虽然我通过接受程序编程的方法冒险投票):你不能 当你说你在语法&#34;中丢失了,那么 我建议保持简单。 (尽管最通用的程序形式在第一眼看上去仍然令人困惑)
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapInvert
{
public static void main(String[] args)
{
Map<Integer, Set<String>> map =
new LinkedHashMap<Integer, Set<String>>();
map.put(1, new LinkedHashSet<String>(Arrays.asList("A","B","C")));
map.put(2, new LinkedHashSet<String>(Arrays.asList("D","E","F")));
map.put(3, new LinkedHashSet<String>(Arrays.asList("G","H","I")));
Map<String, Integer> resultA = inverseEx(map);
System.out.println("Procedural: "+resultA);
Map<String, Integer> resultB = map.entrySet().stream().collect(
LinkedHashMap::new,
(m, e) -> e.getValue().forEach(v -> m.put(v, e.getKey())),
(m0, m1) -> m0.putAll(m1));
System.out.println("Functional: "+resultB);
}
/**
* Invert the given map, by mapping each element of the values to
* the respective key
*
* @param map The input map
* @return The inverted map
*/
private static <K, V> Map<V, K> inverseEx(
Map<K, ? extends Collection<? extends V>> map)
{
Map<V, K> result = new LinkedHashMap<V, K>();
for (Entry<K, ? extends Collection<? extends V>> e : map.entrySet())
{
for (V v : e.getValue())
{
result.put(v, e.getKey());
}
}
return result;
}
}
答案 1 :(得分:2)
这是使用'reduce'的功能版本。由于缺乏持久性数据结构,在功能上执行此操作的主要缺点是导致性能不佳。
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
class Test {
public static <K,V> Map<K,V> combineMaps(Map<K,V> map1, Map<K,V> map2) {
Map<K,V> map = new HashMap<K,V>();
map.putAll(map1);
map.putAll(map2);
return map;
}
public static BiFunction<Map<String,Integer>,Map.Entry<Integer,Set<String>>,Map<String,Integer>> accumulator =
(map, entry) -> combineMaps(map, entry.getValue().stream().collect(Collectors.toMap(k -> k, k -> entry.getKey())));
public static BinaryOperator<Map<String,Integer>> binOperator =
(map1, map2) -> combineMaps(map1, map2);
public static void main(String[] args) {
Set<String> setOne = new HashSet<String>();
setOne.add("one");
setOne.add("two");
setOne.add("three");
Set<String> setTwo = new HashSet<String>();
setTwo.add("four");
setTwo.add("five");
setTwo.add("six");
Map<Integer,Set<String>> myMap = new HashMap<Integer, Set<String>>();
myMap.put(1, setOne);
myMap.put(2, setTwo);
Map<String,Integer> newMap = myMap.entrySet().stream()
.reduce(new HashMap<String,Integer>(), accumulator, binOperator);
System.out.println(newMap.get("five"));
}
}
答案 2 :(得分:0)
我会"cut the knot"在这里,可以这么说,并改变问题的条款。谷歌的优秀Guava library有一个Multimap
interface和一个SetMultimap
子类型,其中包含一些实现。 Multimap
的文档告诉我们:
您可以将多图的内容可视化为从键到非空的值集合的映射:
- a→1,2
- b→3
...或作为单个“扁平化”的键值对集合:
- a→1
- a→2
- b→3
SetMultimap
类型的entries
method会返回Set<Map.Entry<K, V>>
个结果。您可以直接stream()
通过该流和map()
来反转条目,然后使用该流构建逆映射。所以这样的事情(我确信我没有做到最好的方式):
public static <K, V> ImmutableSetMultimap<V, K> invert(SetMultimap<? extends K, ? extends V> input) {
return input
.entries()
.stream()
.map(e -> new Map.Entry<V, K>() {
// This inner class should probably be abstracted out into its own top-level thing
@Override
public V getKey() {
return e.getValue();
}
@Override
public K getValue() {
return e.getKey();
}
@Override
public K setValue(K value) {
throw new UnsupportedOperationException();
}
})
.collect(new ImmutableSetMultimapCollector<>());
}
现在Guava似乎没有达到Java 8的速度,因此您需要编写自己的ImmutableSetMultimapCollector
(或者您想要生成的任何输出类),但这样可以重复使用这是值得的。 This article gives some guidance.
另请注意,通过使用SetMultimap
作为结果类型,我们可以在不丢失信息的情况下反转一个输入,其中相同的值映射到两个不同的键。这可能是一个加分!
所以我在这里强调两个教训:
Multimap
就是这样一种工具。答案 3 :(得分:0)
为简单起见,我们假设您希望将Map<Long, Set<String>>
中String
值唯一的值转换为Map<String, Long>
。
我认为这个操作是一个带有Map<String, Long>
类型累加器的 fold left ,它在Java 8中变为带有累加器和合并器:请参阅Javadoc,以及this related answer。
写作的一种方式是这样的:
public static void main(String[] args) {
Map<Long, Set<String>> map = new HashMap<>();
map.put(1L, new HashSet<String>());
map.get(1L).add("a");
map.get(1L).add("b");
map.put(2L, new HashSet<>());
map.get(2L).add("c");
map.get(2L).add("d");
map.get(2L).add("e");
Map<String, Long> result = map.entrySet().stream().reduce(
new HashMap<String, Long>(),
(accumulator, entry) -> {
// building an accumulator of type Map<String, Long> from a Map.Entry<Long, Set<String>>
entry.getValue().forEach(s -> accumulator.put(s, entry.getKey()));
return accumulator;
},
(accumulator1, accumulator2) -> {
// merging two accumulators of type Map<String, Long>
accumulator1.keySet().forEach(k -> accumulator2.put(k, accumulator1.get(k)));
return accumulator2;
}
);
result.keySet().forEach(k -> System.out.println(k + " -> " + result.get(k)));
}
输出以下内容:
a -> 1
b -> 1
c -> 2
d -> 2
e -> 2
注意:这与其他answer中的想法相同,我之前没有注意到:)