Слияние двух карт - PullRequest
       53

Слияние двух карт

24 голосов
/ 10 января 2012

У меня есть две карты, ключи которых String s, а значения Set<MyObject>. Учитывая два Map s, какой самый простой способ объединить их так, чтобы, если два ключа были идентичны, значение представляло собой объединение двух наборов. Вы можете предположить, что значения никогда не бывают нулевыми, и если это полезно, мы можем сделать эти Map s SortedMap s.

Ответы [ 11 ]

27 голосов
/ 06 августа 2014

Вы можете сделать это с потоком довольно легко:

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

Это разбивает карты на их Entry с, а затем соединяет их с Collector, который разрешает дубликаты , добавляя оба значения к новому HashSet.

Это также работает для любого количества карт.

Некоторые вариации, которые дают одинаковый результат:

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, собранных в.

13 голосов
/ 10 января 2012

Мы говорим о HashMap случаях. В этом случае поиск равен O (1), поэтому вы можете просто взять одну карту, перебрать записи этой карты и посмотреть, содержит ли другая карта этот ключ. Если нет, просто добавьте набор. Если он содержит ключ, возьмите объединение двух наборов (, добавив все элементы одного набора в другой)

Для иллюстрации с некоторым кодом, где я использовал 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() );
  }
}
4 голосов
/ 14 марта 2017
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);
    });
}
4 голосов
/ 10 января 2012

Как насчет этого (не проверено):

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 не синхронизированы, вам нужно искать какое-то другое решение, если вы хотите поточно-ориентированный код.

1 голос
/ 10 января 2012

Примерно так (не проверено):

// 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;
}

Обратите внимание, что это решение не изменяет ни один из своих аргументов.

1 голос
/ 10 января 2012

Следующее должно объединить 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( ));
}

Я не знаю, на что вы параметризовали свои Set s, поэтому <???>: замените соответствующим образом.

0 голосов
/ 28 сентября 2016
<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)
}
0 голосов
/ 24 марта 2016

Если вы определяете метод для объединения ненулевых Set s как:

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

, то объединение двух карт m1 и m2, имеющих значения Set<V>, может быть выполнено следующим образом:

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)));
0 голосов
/ 20 августа 2015

Если вы хотите использовать неизменяемые структуры данных для предотвращения манипуляций с вашей объединенной картой и экземплярами 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();
            }
        }
    );
}
0 голосов
/ 23 июля 2014

Обратите внимание, что в конечном итоге все остальные ответы будут дополнять исходные наборы, которые могут не подходить для всех случаев использования, если вы не хотите, чтобы в качестве выходных данных использовалась третья карта и создавался новый набор для каждого ключа

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