1

Use java8, is there any concise syntax to merge two maps:

    Map<String, Map<String, Set<Long>>> m1

    Map<String, Map<String, Set<Long>>> m2

And do not change any element in m1, m2, even change the mergedMap.

For example:

the m1 contains 2 elements which like:

{
    "k1": {
        "v1": [
            11, 
            12
        ]
    }, 
    "k2": {
        "v2": [
            21
        ]
    }
}

the m2 contains 3 elements which like:

{
    "k1": {
        "v11": [
            11
        ]
    }, 
    "k2": {
        "v2": [
            21, 
            22
        ]
    }, 
    "k3": {
        "v3": [
            31
        ]
    }
}

The merged map what I want is the merged 3 elements.

Especially, the "k1"'s value combine from m1 and m2.

{
    "k1": {
        "v1": [
            11, 
            12
        ], 
        "v11": [
            11
        ]
    }, 
    "k2": {
        "v2": [
            21, 
            22
        ]
    }, 
    "k3": {
        "v3": [
            31
        ]
    }
}

And when I add some elements in the merged Map which key is <"k3", <"v3", ???>>.

I don't want the origin map m2's elements modify any more.

free斩
  • 421
  • 1
  • 6
  • 18

3 Answers3

4

Merging nested collections without modifying the source collections is trickier than it might look at the first glance. When performing the merging in-place, modifying one of the source maps, a nested application of Map.merge will do, but for collecting the nested data into a new container, a construct similar to the Java 9, flatMapping collector is needed. With this collector, the solution could look like:

Map<String, Map<String, Set<Long>>> m3 =
  Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
    .collect(groupingBy(Map.Entry::getKey,
      flatMapping(e -> e.getValue().entrySet().stream(),
        groupingBy(Map.Entry::getKey, flatMapping(e -> e.getValue().stream(), toSet())))));

This also works under Java 8 when using a back-port of the flatMapping collector (see the end of this answer).

An alternative is to create collectors specialized to this use case, considering that the number of Maps to merge is rather small:

static <T> Collector<Set<T>,?,Set<T>> mergeSets() {
    return Collector.of(HashSet::new, Set::addAll, (s1,s2)->{ s1.addAll(s2); return s1; });
}
static <K,V> Collector<Map<K,V>,?,Map<K,V>> mergeMaps(Collector<V,?,V> c) {
    return collectingAndThen(reducing(
        (m1,m2) -> Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
            .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, c)))),
        o -> o.orElse(Collections.emptyMap()));
}

These collectors can be combined to do the job:

Map<String, Map<String, Set<Long>>> m3 =
    Stream.of(m1, m2).collect(mergeMaps(mergeMaps(mergeSets())));
Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
2

Kind of:

HashMap<String, Map<String, Set<String>>> m1 = new HashMap<>();
HashMap<String, Map<String, Set<String>>> m2 = new HashMap<>();

Stream.of(m1,m2).flatMap( m -> m.entrySet().stream())
        // stream of entries <String,Set<String>
        .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (s1, s2) -> { /* do whatever  you like to merge your nested  maps */ return new HashMap<>()}  )

`

And of course, you will have to merge nested maps. This will leave original data structures as they were

Konstantin Pribluda
  • 12,329
  • 1
  • 30
  • 35
1

Streams were not meant to be a replacement for everything, so here is my view :

for (Map.Entry<String, Map<String, Set<Long>>> entry : m2.entrySet()) {
        m1.merge(entry.getKey(), entry.getValue(), (v1, v2) -> {
            for (Map.Entry<String, Set<Long>> e : v2.entrySet()) {
                v1.merge(e.getKey(), e.getValue(), (s1, s2) -> {
                    s1.addAll(s2);
                    return s1;
                });
            }
            return v1;
        });
    }

Or using flatMap as in the other suggested answer:

          Stream.of(m1, m2)
            .flatMap(map -> map.entrySet().stream())
            .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> {
                for (Map.Entry<String, Set<Long>> e : v2.entrySet()) {
                    v1.merge(e.getKey(), e.getValue(), (s1, s2) -> {
                        s1.addAll(s2);
                        return s1;
                    });
                }
                return v1;
            }, HashMap::new));

Having your input:

[k1={v1=[11, 12]}, k2={v2=[21]}]
{k1={v11=[11]}, k2={v2=[21, 22]}, k3={v3=[31]}}

Output would be:

   {k1={v11=[11], v1=[11, 12]}, k2={v2=[21, 22]}, k3={v3=[31]}}
Eugene
  • 117,005
  • 15
  • 201
  • 306