合并两个地图

Posted

技术标签:

【中文标题】合并两个地图【英文标题】:Merging two Maps 【发布时间】:2012-02-06 10:22:07 【问题描述】:

我有两张地图,其键为Strings,其值为Set<MyObject>。给定两个Maps,合并它们的最简单方法是什么,如果两个键相同,则值是两个集合的并集。您可以假设值永远不会为空,如果有用,我们可以将这些设置为 Maps SortedMaps。

【问题讨论】:

如果你有可能使用GuavasMultimap,你可以简单地避免这个问题,合并就像putAll(Multimap other)一样简单。 类似,可能重复:***.com/questions/4299728/… 使用Map merge 方法应该很容易做到。 【参考方案1】:

您可以很容易地使用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()),避免使用mapflatMap【参考方案2】:

我们在谈论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() );
  

【讨论】:

这将跳过存在于 secondMap 但不存在于 firstMap 中的条目 sam - 他正在原地编辑 secondMap,因此 secondMap 将被更改。 你可以使用 - addAll 方法 download.oracle.com/javase/6/docs/api/java/util/HashMap.html 但是总是有这个问题 - 如果你的两个哈希映射有任何相同的键 - 那么它将覆盖第一个哈希映射的键的值第二个哈希映射中的键值。为了更安全-更改键值-您可以在键上使用前缀或后缀-(第一个哈希映射的前缀/后缀不同,第二个哈希映射的前缀/后缀不同)【参考方案3】:
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&lt;V value1, V value2, V result&gt; 。所以正确的答案应该是 `map1.forEach((key1, value1) -> map2.merge(key1, value1, (value1, value2) -> doSomethingWithValues); );【参考方案4】:

这个怎么样(未经测试):

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() 都是MapSet 中的可选方法。因此(并且为了获得 O(1) 查找),我建议使用 HashMapHashSet

请注意,因为HashSetHashMap 都不是同步的,所以如果您需要线程安全代码,则需要寻找其他解决方案。

【讨论】:

【参考方案5】:

以下应将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 参数化为什么,因此&lt;???&gt;: 酌情替换。

【讨论】:

【参考方案6】:

类似这样的东西(未经测试):

// 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(),但m2Map,因此没有getValue() 方法。 @MichaelMcGowan:哦,对了,也解决了这个问题(天哪,看看当我尝试编写代码时会发生什么!)【参考方案7】:
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);

【讨论】:

这是一个解释如何合并两张地图的简单程序【参考方案8】:

请注意,所有其他答案最终会增加您可能不希望所有用例的原始集合,如果您不希望使用第三个映射作为输出并为每个键创建一个新集合

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());
    

【讨论】:

【参考方案9】:

如果您希望最终使用不可变的数据结构来防止操作合并的地图和地图的 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();
            
        
    );

【讨论】:

【参考方案10】:

如果你定义一个方法来联合非空Sets 为:

static <T> Set<T> union(Set<T>... sets) 
    return Stream.of(sets)
                 .filter(s -> s != null)
                 .flatMap(Set::stream)
                 .collect(Collectors.toSet());

然后合并两个具有Set&lt;V&gt;值的映射m1m2可以如下执行:

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)));

【讨论】:

【参考方案11】:
<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)

【讨论】:

以上是关于合并两个地图的主要内容,如果未能解决你的问题,请参考以下文章

如何在打字稿中合并或连接两个地图列表? [复制]

在scala中合并两个嵌套地图

合并两个地图并对相同键的值求和的最佳方法?

在Java中合并两个嵌套映射

Flutter:如何合并两个对象并对同一个key的值求和?

安卓工作室。合并两个项目