【发布时间】:2012-02-06 10:22:07
【问题描述】:
我有两张地图,其键为Strings,其值为Set<MyObject>。给定两个Maps,合并它们的最简单方法是什么,如果两个键相同,则值是两个集合的并集。您可以假设值永远不会为空,如果有用,我们可以将这些设置为 Maps SortedMaps。
【问题讨论】:
我有两张地图,其键为Strings,其值为Set<MyObject>。给定两个Maps,合并它们的最简单方法是什么,如果两个键相同,则值是两个集合的并集。您可以假设值永远不会为空,如果有用,我们可以将这些设置为 Maps SortedMaps。
【问题讨论】:
您可以很容易地使用stream 做到这一点:
Map<T, Set<U>> merged = Stream.of(first, second)
.map(Map::entrySet)
.flatMap(Set::stream)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> {
HashSet<U> both = new HashSet<>(a);
both.addAll(b);
return both;
}));
这会将地图拆分为它们的Entrys,然后通过将两个值添加到新的HashSet 来将它们与Collector 和resolves duplicates 连接起来。
这也适用于任意数量的地图。
产生相同结果的一些变体:
Stream.of(first, second).flatMap(m -> m.entrySet().stream())
.collect(...);
Stream.concat(first.entrySet().stream(), second.entrySet().stream())
.collect(...); //from comment by Aleksandr Dubinsky
如果没有重复键,Collectors.toMap 的第三个参数不是必需的。
还有一个Collectors.toMap 带有第四个参数,可让您决定收集到的Map 的类型。
【讨论】:
Stream.concat (first.entrySet().stream(), second.entrySet().stream()),避免使用map和flatMap。
我们在谈论HashMap 实例吗?在这种情况下,查找是 O(1),所以你可以只取一个映射,遍历该映射的条目,查看另一个映射是否包含该键。如果没有,只需添加集合。如果它包含密钥,则取两组的并集(由一组的adding all elements 到另一组)
为了说明一些代码,我使用 Set 在我的 IDE 中进行自动完成
Map<String, Set<Double>> firstMap = new HashMap<String, Set<Double>>( );
Map<String, Set<Double>> secondMap = new HashMap<String, Set<Double>>( );
Set<Map.Entry<String, Set<Double>>> entries = firstMap.entrySet();
for ( Map.Entry<String, Set<Double>> entry : entries ) {
Set<Double> secondMapValue = secondMap.get( entry.getKey() );
if ( secondMapValue == null ) {
secondMap.put( entry.getKey(), entry.getValue() );
}
else {
secondMapValue.addAll( entry.getValue() );
}
}
【讨论】:
static void mergeSet(Map<String, Set<String>> map1, Map<String, Set<String>> map2) {
map1.forEach((key1, value1) -> {
map2.merge(key1, value1, (key2, value2) -> key2).addAll(value1);
});
}
【讨论】:
merge(T key, V value, Bifunction<V value1, V value2, V result> 。所以正确的答案应该是 `map1.forEach((key1, value1) -> { map2.merge(key1, value1, (value1, value2) -> doSomethingWithValues); });
这个怎么样(未经测试):
Map<String,Set<Whatever>> m1 = // input map
Map<String,Set<Whatever>> m2 = // input map
Map<String,Set<Whatever>> ret = // new empty map
ret.putAll(m1);
for(String key : m2.keySet()) {
if(ret.containsKey(key)) {
ret.get(key).addAll(m2.get(key));
} else {
ret.put(key,m2.get(key));
}
}
这个解决方案不修改输入映射,而且因为它很短并且只依赖于 API 方法,我觉得它的可读性很好。
注意putAll() 和addAll() 都是Map 和Set 中的可选方法。因此(并且为了获得 O(1) 查找),我建议使用 HashMap 和 HashSet。
请注意,因为HashSet 或HashMap 都不是同步的,所以如果您需要线程安全代码,则需要寻找其他解决方案。
【讨论】:
以下应将map1 合并到map2(未经测试):
for (Entry<String, Set<???>> entry : map1.entrySet( ))
{
Set<???> otherSet = map2.get(entry.getKey( ));
if (otherSet == null)
map2.put(entry.getKey( ), entry.getValue ( ));
else
otherSet.addAll(entry.getValue( ));
}
我不知道您将Sets 参数化为什么,因此<???>: 酌情替换。
【讨论】:
类似这样的东西(未经测试):
// Assume all maps are of the same generic type.
public static Map<String, Set<MyObject>> mergeAll(Map m1, Map m2) {
Map<String, Set<MyObject>> merged = new HashMap();
// Merge commom entries into the new map.
for (Map.Entry<String, Set<MyObject>> entry : m1.entrySet()) {
String key = entry.getKey();
Set<MyObject> s1 = new HashSet(entry.getValue());
Set<MyObject> s2 = m2.get(key);
if (s2 != null) s1.addAll(s2);
merged.put(key, s1);
}
// Add entries unique to m2 to the new map.
for (String key : m2.keys()) {
if (!s1.containsKey(key)) merged.put(key, new HashSet(m2.get(key)));
}
return merged;
}
请注意,此解决方案不会改变其任何一个参数。
【讨论】:
m2 但不在m1 中的键呢?
m2.getValue(),但m2 是Map,因此没有getValue() 方法。
Map<Integer,String> m1=new HashMap<Integer,String>();
Map<Integer,String> m2=new HashMap<Integer,String>();
m1.put(1,"one");
m1.put(2,"two");
m2.put(3,"three");
m2.put(2,"two");
Set<Integer> s=m2.keySet();
for(int i:s){
if(m1.get(i)==null){
m1.put(i,m2.get(i));
}
}
System.out.println(m1);
【讨论】:
请注意,所有其他答案最终会增加您可能不希望所有用例的原始集合,如果您不希望使用第三个映射作为输出并为每个键创建一个新集合
public static void merge2Maps(Map<String, Set<Double>> a, Map<String, Set<Double>> b, Map<String, Set<Double>> c){
for (Map.Entry<String, Set<Double>> entry : a.entrySet()) {
Set<Double> set = new HashSet<Double>();
c.put(entry.getKey(), set);
set.addAll(entry.getValue());
}
for (Map.Entry<String, Set<Double>> entry : b.entrySet()) {
String key = entry.getKey();
Set<Double> set = c.get(key);
if (set == null) {
set = new HashSet<Double>();
c.put(entry.getKey(), set);
}
set.addAll(entry.getValue());
}
}
【讨论】:
如果您希望最终使用不可变的数据结构来防止操作合并的地图和地图的 Set 实例,那么您可以采用这种方法。此解决方案使用 Google 的 Guava 库。
public <K,T> Map<K, Set<T>> mergeToImmutable (
final Map<K, Set<T>> left,
final Map<K, Set<T>> right)
{
return Maps.toMap(
Sets.union(
checkNotNull(left).keySet(),
checkNotNull(right).keySet()
),
new Function<K, Set<T>> () {
@Override
public Set<T> apply (K input) {
return ImmutableSet.<T>builder()
.addAll(MoreObjects.firstNonNull(left.get(input), Collections.<T>emptySet()))
.addAll(MoreObjects.firstNonNull(right.get(input), Collections.<T>emptySet()))
.build();
}
}
);
}
【讨论】:
如果你定义一个方法来联合非空Sets 为:
static <T> Set<T> union(Set<T>... sets) {
return Stream.of(sets)
.filter(s -> s != null)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
然后合并两个具有Set<V>值的映射m1和m2可以如下执行:
Map<String, V> merged
= union(m1.keySet(), m2.keySet())
.stream()
.collect(Collectors.toMap(k -> k, k -> union(m1.get(k), m2.get(k))));
甚至更简单:
Map<String, V> merged = new HashMap<>();
for (String k : union(m1.keySet(), m2.keySet())
merged.put(k, union(m1.get(k), m2.get(k)));
【讨论】:
<K, V> Map<K, List<V>> mergeMapOfLists(Stream<Map<K, List<V>>> stream) {
return stream
.map(Map::entrySet) // convert each map to set of map's entries
.flatMap(Collection::stream) // convert each map entry to stream and flat them to one stream
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
(list1, list2) -> {
list1.addAll(list2);
return list1;
})); // convert stream to map; if key is duplicated execute merge fuction (append exisitng list with elements from new list)
}
【讨论】: