Merge two maps with Java 8 - java

I have two maps like this:
map1 = new Map<String, MyObject>();
map2 = new Map<String, MyObject>();
MyObject {
Integer mark1;
Integer mark2;
}
What I want to do to is to merge the two maps into a map3 <String, MyObject> like this:
If map1.place is not in map2.place, then I add the entry to map3.
same if map2.place is not in map1.place, I add the entry to map3.
if map1.place is in map2.place, then I add this entry:
map1.place, (map1.mark1, map2.mark2)
I have read about flatMap, but I really have a hard time using it.
Any clue how to do this?

It can be done using the Stream API with the appropriate mergeFunction as next:
Map<String, MyObject> map3 = Stream.of(map1, map2)
.flatMap(map -> map.entrySet().stream())
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> new MyObject(v1.getMark1(), v2.getMark2())
)
);
This concatenates entries of map1 followed by the entries of map2, then convert everything as a Map with a merge function that will use mark1 from the first value (the one from map1) and mark2 from the second value (the one from map2) in case of duplicate keys.
Or it could also be done using a different Supplier<Map> that will propose a map that already contains the entries of map1 then we can focus only on adding the entries of map2 as next:
Map<String, MyObject> map3 = map2.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> new MyObject(v1.getMark1(), v2.getMark2()),
() -> new HashMap<>(map1)
)
);

Here is what I think would work
Map<String, MyObj> map3 = new HashMap<>(map1);
map2.forEach(
(key, value) -> map3.merge(key, value, (v1, v2) -> new MyObject(v1.mark1,v2.mark2))
);
The merge function is what is taking care of your scenario 3, in that if the key already exists, it creates a new MyObject with v1.mark1 and v2.mark2

Something like this should work.
Map<String, MyObject> result = new HashMap<String, MyObject>();
Set<String> allKeys = new HashSet<String>();
allKeys.addAll(map1.keySet());
allKeys.addAll(map2.keySet());
for(String key : allKeys){
MyObject v1 = map1.get(key);
MyObject v2 = map2.get(key);
if(v1 != null && v2 == null){
result.put(key, v1);
}else if(v1 == null && v2 !=null){
result.put(key, v2);
} else {
MyObject newObject = new MyObject(v1.mark1, v2.mark2);
result.put(key, newObject);
}
}

Incase of a simple merge you could use map3.putAll() as explained in How can I combine two HashMap objects containing the same types?
In your case, you would probably have to write some custom logic,
First populate map3 with map1. Then Iterate the map3 to find any duplicates with map2 in which case you replace the entry with the map1.place, (map1.mark1, map2.mark2) logic.
MapMerge
public class MapMerge {
public static void main(String []args){
Map<String, MyObject> map1 = new HashMap<String, MyObject>();
Map<String, MyObject> map2 = new HashMap<String, MyObject>();
Map<String, MyObject> map3 = new HashMap<String, MyObject>();
map3.putAll(map1);
for(Entry<String, MyObject> entry:map2.entrySet()){
if (map3.containsKey(entry.getKey())){
MyObject map3Obj = map3.get(entry.getKey());
map3.put(
entry.getKey(),
new MyObject(map3Obj.getMark1(),entry.getValue().getMark2())
);
}
}
}
}
MyObject
class MyObject{
public MyObject(Integer m1, Integer m2){
mark1 = m1;
mark2 = m2;
}
public Integer getMark1() {
return mark1;
}
public void setMark1(Integer mark1) {
this.mark1 = mark1;
}
public Integer getMark2() {
return mark2;
}
public void setMark2(Integer mark2) {
this.mark2 = mark2;
}
Integer mark1;
Integer mark2;
}

Case 1: Given a List of Maps. Then Join the maps according to the Key
public class Test14 {
public static void main(String[] args) {
Map<String, List<Integer>> m1 = new HashMap<>();
Map<String, List<Integer>> m2 = new HashMap<>();
m1.put("a", List.of(1));
m1.put("b", List.of(2, 3));
m2.put("a", List.of(12, 115));
m2.put("b", List.of(2, 5));
m2.put("c", List.of(6));
System.out.println("map1 => " + m1);
System.out.println("map2 => " + m2);
ArrayList<Map<String, List<Integer>>> maplist = new ArrayList<Map<String, List<Integer>>>();
maplist.add(m1);
// map1 => {a=[1], b=[2, 3]}
maplist.add(m2);
// map2 => {a=[12, 115], b=[2, 5], c=[6]}
System.out.println("maplist => " + maplist);
// maplist => [{a=[1], b=[2, 3]}, {a=[12, 115], b=[2, 5], c=[6]}]
// flatmap does omitted {}
List<Entry<String, List<Integer>>> collect11 =
maplist
.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toList());
System.out.println(" collect11 => " + collect11);
// collect11 => [a=[1], b=[2, 3], a=[12, 115], b=[2, 5], c=[6]]
// That's why we will use this flatmap
Map<String, List<Integer>> map2 = maplist.stream()
.flatMap(map -> map.entrySet().stream())
.collect(
Collectors.toMap(
//keyMapper
Map.Entry::getKey,
//valueMapper
Map.Entry::getValue,
(list_a,list_b) -> Stream.concat(list_a.stream(), list_b.stream())
.collect(Collectors.toList())
)//tomap
);
//{a=[1, 12, 115], b=[2, 3, 2, 5], c=[6]}
System.out.println("After joining the maps according the key => " + map2);
// After joining the maps according the key => {a=[1, 12, 115], b=[2, 3, 2, 5], c=[6]}
/*
OUTPUT :
After joining the maps according the key => {a=[1, 12, 115], b=[2, 3, 2, 5], c=[6]}
*/
}// main
}

Related

Inserting Map into Map using streams

I'd like to know how I can insert a map into another map using streams in java.
I have a two maps
Map<String, List<Character>> map1
Map<String, List<Integer>> map2
I d like to merge both maps such that we have
Map<String, Map<Character, Integer>> finalmap
if map1 is something like
{String1 = [Character1, Character2], String2 = [Character3, Character4], etc}
and map2 is
{String1 = [Integer1, Integer2], String2 = [Integer3, Integer4], etc}
I want it to merge such that the innermap maps Character1 with Integer1 and so on.
Does someone have an idea how to solve this problem? :)
Map<String, Map<Character, Integer>> map3 = map1.entrySet()
.stream()
.flatMap(entry -> {
if (map2.containsKey(entry.getKey())) {
List<Integer> integers = map2.get(entry.getKey());
List<Character> characters = entry.getValue();
Map<Character, Integer> innerMap = IntStream.range(0, Math.min(integers.size(), characters.size()))
.mapToObj(i -> Map.entry(characters.get(i), integers.get(i)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return Stream.of(Map.entry(entry.getKey(), innerMap));
}
return Stream.empty();
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
This is a bit late but in case the length of Character and Integer lists is different, it may be possible to build an inner map containing all Character keys and null for missing Integer values:
// class MyClass
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey())) // keep the keys from both maps
.map(e -> Map.entry(
e.getKey(), // String key for result
IntStream.range(0, e.getValue().size()) // build map <Character, Integer>
.mapToObj(i -> new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
))
// use custom collector to allow null Integer values
.collect(
MyClass::innerMap,
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()),
Map::putAll
)
))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
static LinkedHashMap<Character, Integer> innerMap() {
return new LinkedHashMap<>();
}
Custom collector for inner map is needed to allow adding null values which is not possible with Collectors.toMap where NPE is thrown.
Tests:
Map<String, List<Character>> map1 = Map.of(
"S0", Arrays.asList('#', '#'),
"S1", Arrays.asList('a', 'b'),
"S2", Arrays.asList('c', 'd'),
"S3", Arrays.asList('e', 'f')
);
System.out.println("map1=" + map1);
Map<String, List<Integer>> map2 = Map.of(
"S1", Arrays.asList(1),
"S2", Arrays.asList(3, 4, 5),
"S3", Arrays.asList(5, 6),
"S4", Arrays.asList(7, 8)
);
System.out.println("map2=" + map2);
Map<String, Map<Character, Integer>> res = joinMaps(map1, map2);
System.out.println("----\nResult:");
res.forEach((k, v) -> System.out.printf("%s -> %s%n", k, v));
Output:
map1={S0=[#, #], S1=[a, b], S2=[c, d], S3=[e, f]}
map2={S1=[1], S2=[3, 4, 5], S3=[5, 6], S4=[7, 8]}
----
Result:
S1 -> {a=1, b=null}
S2 -> {c=3, d=4}
S3 -> {e=5, f=6}
Update
Another solution using Stream::flatMap and groupingBy + mapping collectors with a custom collector used as a downstream collector is shown below:
static Map<String, Map<Character, Integer>> joinMaps(
Map<String, List<Character>> map1, Map<String, List<Integer>> map2)
{
return map1
.entrySet()
.stream()
.filter(e -> map2.containsKey(e.getKey()))
.flatMap(e -> IntStream.range(0, e.getValue().size())
.mapToObj(i -> Map.entry(
e.getKey(),
new AbstractMap.SimpleEntry<>(
e.getValue().get(i),
i < map2.get(e.getKey()).size() ? map2.get(e.getKey()).get(i) : null
)
))) // Stream<Map.Entry<String, Map.Entry<Character, Integer>>>
.collect(Collectors.groupingBy(
Map.Entry::getKey, // use String as key in outer map
Collectors.mapping(e -> e.getValue(), // build inner map
Collector.of( // using custom collector
MyClass::innerMap, // supplier
(hm, e2) -> hm.put(e2.getKey(), e2.getValue()), // accumulator
MyClass::mergeMaps // combiner
))
));
}
static <T extends Map> T mergeMaps(T acc, T map) {
acc.putAll(map);
return acc;
}
Here mergeMaps is a BinaryOperator<A> combiner argument in the method Collector.of which slightly differs from Stream::collect used above where BiConsumer<R, R> is used as combiner.

How to print HashMap values in descending order, but if two or more values are equal, print them by keys ascending? (JAVA)

For example we have
Map<String, Integer> map = new HashMap<>();
map.put("fragments", 5);
map.put("motes", 3);
map.put("shards", 5);
I want to print them like this:
fragments: 5
shards: 5
motes: 3
I would solve this by first putting the values in a TreeMap
Then I would sort the keys based on equal values and put them in a
LinkedHashMap to preserve the order.
Map<String, Integer> map = new TreeMap<>();
map.put("motes", 3);
map.put("shards", 5);
map.put("fragments", 5);
map = map.entrySet().stream().sorted(Comparator.comparing(
Entry<String, Integer>::getValue).reversed()).collect(
LinkedHashMap<String, Integer>::new,
(map1, e) -> map1.put(e.getKey(), e.getValue()),
LinkedHashMap::putAll);
map.entrySet().forEach(System.out::println);
Based on the excellent answer here, consider the following solution:
public static void main(String[] args) {
final Map<String, Integer> originalMap = new HashMap<>();
originalMap.put("fragments", 5);
originalMap.put("motes", 3);
originalMap.put("shards", 5);
final Map<String, Integer> sortedMap = sortByValue(originalMap, false);
sortedMap
.entrySet()
.stream()
.forEach((entry) -> System.out.println(entry.getKey() + " : " + entry.getValue()));
}
private static Map<String, Integer> sortByValue(Map<String, Integer> unsortedMap, final boolean ascending) {
List<Entry<String, Integer>> list = new LinkedList<>(unsortedMap.entrySet());
// Sorting the list based on values
list.sort((o1, o2) -> ascending ? o1.getValue().compareTo(o2.getValue()) == 0
? o1.getKey().compareTo(o2.getKey())
: o1.getValue().compareTo(o2.getValue()) : o2.getValue().compareTo(o1.getValue()) == 0
? o2.getKey().compareTo(o1.getKey())
: o2.getValue().compareTo(o1.getValue()));
return list.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new));
}

Collectors.groupby for Map<String,List<String>

Forgive me if the solution is very obvious but I can't seem to figure out how to do this
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("b1", "a1");
map.put("b2", "a2");
map.put("b3", "a1");
Map<String, List<String>> mm = map.values().stream().collect(Collectors.groupingBy(m -> m));
System.out.println(mm);
}
I want to group by based on values in hashmap. I want the output to be {a1=[b1, b3], a2=[b2]} but it is currently coming as {a1=[a1, a1], a2=[a2]}
Currently, you're streaming over the map values (which I assume is a typo), based on your required output you should stream over the map entrySet and use groupingBy based on the map value's and mapping as a downstream collector based on the map key's:
Map<String, List<String>> result = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey,
Collectors.toList())));
You could also perform this logic without a stream via forEach + computeIfAbsent:
Map<String, List<String>> result = new HashMap<>();
map.forEach((k, v) -> result.computeIfAbsent(v, x -> new ArrayList<>()).add(k));
You can use Collectors.mapping with Collectors.groupingBy on the entrySet of the map as :
Map<String, List<String>> mm = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
but it is currently coming as {a1=[a1, a1], a2=[a2]}
That's because you are currently grouping on the values collection which is {a1, a2, a1} only.
public class Test5 {
public static void main(String[] args) {
List<String> list1 = List.of("Tabu", "Gina", "protijayi", "Gini", "Gini","North Calcutta");
List<String> list2 = List.of("Soudipta", "Gina", "Gina", "upto");
List<String> list3 = List.of("Soudipta", "Gina", "protijayi", "Tabu","South Calcutta");
List<List<String>> listres = List.of(list1, list2, list3);
System.out.println(listres);
/*
[[Tabu, Gina, protijayi, Gini, Gini, North Calcutta],
[Soudipta, Gina, Gina, upto],
[Soudipta, Gina, protijayi, Tabu, South Calcutta]]
*/
Map<String, List<Long>> FirstKeyThenValue1 = listres.stream().flatMap(
// mapper
list -> list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().parallelStream()
).collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(
// mapper, downstream
Entry::getValue, Collectors.toList())));
System.out.println("FirstKeyThenValue1 -> " + FirstKeyThenValue1);
/*
{
upto=[1],
Soudipta=[1, 1],
Gina=[1, 2, 1],
Tabu=[1, 1],
North Calcutta=[1],
South Calcutta=[1],
protijayi=[1, 1],
Gini=[2]}
*/
Map<Long, List<String>> FirstValueThenkey1 = listres.stream().flatMap(
// mapper
list -> list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
).collect(
Collectors.groupingBy(Entry::getValue, Collectors.mapping(
Entry::getKey, Collectors.toList()
))
);
System.out.println(" FirstValueThenkey1 => " + FirstValueThenkey1);
/*
{
1=[Gina, Tabu, North Calcutta, protijayi, upto, Soudipta,
Soudipta, Gina, Tabu, South Calcutta, protijayi],
2=[Gini, Gina]
}
*/
}// main
}

Merge list of maps using Java 8 Stream API

I'm trying to learn Java 8 Stream and when I try to convert some function to java8 to practice. I meet a problem.
I'm curious that how can I convert follow code to java stream format.
/*
* input example:
* [
{
"k1": { "kk1": 1, "kk2": 2},
"k2": {"kk1": 3, "kk2": 4}
}
{
"k1": { "kk1": 10, "kk2": 20},
"k2": {"kk1": 30, "kk2": 40}
}
]
* output:
* {
"k1": { "kk1": 11, "kk2": 22},
"k2": {"kk1": 33, "kk2": 44}
}
*
*
*/
private static Map<String, Map<String, Long>> mergeMapsValue(List<Map<String, Map<String, Long>>> valueList) {
Set<String> keys_1 = valueList.get(0).keySet();
Set<String> keys_2 = valueList.get(0).entrySet().iterator().next().getValue().keySet();
Map<String, Map<String, Long>> result = new HashMap<>();
for (String k1: keys_1) {
result.put(k1, new HashMap<>());
for (String k2: keys_2) {
long total = 0;
for (Map<String, Map<String, Long>> mmap: valueList) {
Map<String, Long> m = mmap.get(k1);
if (m != null && m.get(k2) != null) {
total += m.get(k2);
}
}
result.get(k1).put(k2, total);
}
}
return result;
}
The trick here is to collect correctly the inner maps. The workflow would be:
Flat map the list of map List<Map<String, Map<String, Long>>> into a stream of map entries Stream<Map.Entry<String, Map<String, Long>>>.
Group by the key of each of those entry, and for the values mapped to same key, merge the two maps together.
Collecting maps by merging them would ideally warrant a flatMapping collector, which unfortunately doesn't exist in Java 8, although it will exist in Java 9 (see JDK-8071600). For Java 8, it is possible to use the one provided by the StreamEx library (and use MoreCollectors.flatMapping in the following code).
private static Map<String, Map<String, Long>> mergeMapsValue(List<Map<String, Map<String, Long>>> valueList) {
return valueList.stream()
.flatMap(e -> e.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.flatMapping(
e -> e.getValue().entrySet().stream(),
Collectors.<Map.Entry<String,Long>,String,Long>toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum)
)
));
}
Without using this convenient collector, we can still build our own with equivalent semantics:
private static Map<String, Map<String, Long>> mergeMapsValue2(List<Map<String, Map<String, Long>>> valueList) {
return valueList.stream()
.flatMap(e -> e.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collector.of(
HashMap::new,
(r, t) -> t.getValue().forEach((k, v) -> r.merge(k, v, Long::sum)),
(r1, r2) -> { r2.forEach((k, v) -> r1.merge(k, v, Long::sum)); return r1; }
)
));
}
As a starting point, converting to use computeIfAbsent and merge gives us the following:
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
final Map<K1, Map<K2, Long>> result = new HashMap<>();
for (final Map<K1, Map<K2, Long>> map : valueList) {
for (final Map.Entry<K1, Map<K2, Long>> sub : map.entrySet()) {
for (final Map.Entry<K2, Long> subsub : sub.getValue().entrySet()) {
result.computeIfAbsent(sub.getKey(), k1 -> new HashMap<>())
.merge(subsub.getKey(), subsub.getValue(), Long::sum);
}
}
}
return result;
}
This removes much of the logic from your inner loop.
This code below is wrong, I leave it here for reference.
Converting to the Stream API is not going to make it neater, but lets give it a go.
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
private static <K1, K2> Map<K1, Map<K2, Long>> mergeMapsValue(List<Map<K1, Map<K2, Long>>> valueList) {
return valueList.stream()
.flatMap(v -> v.entrySet().stream())
.collect(groupingBy(Entry::getKey, collectingAndThen(mapping(Entry::getValue, toList()), l -> l.stream()
.reduce(new HashMap<>(), (l2, r2) -> {
r2.forEach((k, v) -> l2.merge(k, v, Long::sum);
return l2;
}))));
}
This is what I've managed to come up with - it's horrible. The problem is that with the foreach approach, you have a reference to each level of the iteration - this makes the logic simple. With the functional approach, you need to consider each folding operation separately.
How does it work?
We first stream() our List<Map<K1, Map<K2, Long>>>, giving a Stream<Map<K1, Map<K2, Long>>>. Next we flatMap each element, giving a Stream<Entry<K1, Map<K2, Long>>> - so we flatten the first dimension. But we cannot flatten further as we need to K1 value.
So we then use collect(groupingBy) on the K1 value giving us a Map<K1, SOMETHING> - what is something?
Well, first we use a mapping(Entry::getValue, toList()) to give us a Map<K1, List<Map<K2, Long>>>. We then use collectingAndThen to take that List<Map<K2, Long>> and reduce it. Note that this means we produce an intermediate List, which is wasteful - you could get around this by using a custom Collector.
For this we use List.stream().reduce(a, b) where a is the initial value and b is the "fold" operation. a is set to new HashMap<>() and b takes two values: either the initial value or the result of the previous application of the function and the current item in the List. So we, for each item in the List use Map.merge to combine the values.
I would say that this approach is more or less illegible - you won't be able to decipher it in a few hours time, let alone a few days.
I took the flatMap(e -> e.entrySet().stream()) part from Tunaki, but used a shorter variant for the collector:
Map<String, Integer> merged = maps.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
More elaborate example:
Map<String, Integer> a = new HashMap<String, Integer>() {{
put("a", 2);
put("b", 5);
}};
Map<String, Integer> b = new HashMap<String, Integer>() {{
put("a", 7);
}};
List<Map<String, Integer>> maps = Arrays.asList(a, b);
Map<String, Integer> merged = maps.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
assert merged.get("a") == 9;
assert merged.get("b") == 5;

Merge Map<String, List<String> Java 8 Stream

I would like to merge two Map with JAVA 8 Stream:
Map<String, List<String>> mapGlobal = new HashMap<String, List<String>>();
Map<String, List<String>> mapAdded = new HashMap<String, List<String>>();
I try to use this implementation:
mapGlobal = Stream.of(mapGlobal, mapAdded)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())
));
However, this implementation only create a result like:
Map<String, List<Object>>
If one key is not contained in the mapGlobal, it would be added as a new key with the corresponding List of String. If the key is duplicated in mapGlobal and mapAdded, both list of values will be merge as: A = {1, 3, 5, 7} and B = {1, 2, 4, 6} then A ∪ B = {1, 2, 3, 4, 5, 6, 7}.
You can do this by iterating over all the entries in mapAdded and merging them into mapGlobal.
The following iterates over the entries of mapAdded by calling forEach(action) where the action consumes the key and value of each entry. For each entry, we call merge(key, value, remappingFunction) on mapGlobal: this will either create the entry under the key k and value v if the key didn't exist or it will invoke the given remapping function if they already existed. This function takes the 2 lists to merge, which in this case, are first added to a TreeSet to ensure both unique and sorted elements and converted back into a list:
mapAdded.forEach((k, v) -> mapGlobal.merge(k, v, (v1, v2) -> {
Set<String> set = new TreeSet<>(v1);
set.addAll(v2);
return new ArrayList<>(set);
}));
If you want to run that potentially in parallel, you can create a Stream pipeline by getting the entrySet() and calling parallelStream() on it. But then, you need to make sure to use a map that supports concurrency for mapGlobal, like a ConcurrentHashMap.
ConcurrentMap<String, List<String>> mapGlobal = new ConcurrentHashMap<>();
// ...
mapAdded.entrySet().parallelStream().forEach(e -> mapGlobal.merge(e.getKey(), e.getValue(), (v1, v2) -> {
Set<String> set = new TreeSet<>(v1);
set.addAll(v2);
return new ArrayList<>(set);
}));
Using foreach over Map can be used to merge given arraylist.
public Map<String, ArrayList<String>> merge(Map<String, ArrayList<String>> map1, Map<String, ArrayList<String>> map2) {
Map<String, ArrayList<String>> map = new HashMap<>();
map.putAll(map1);
map2.forEach((key , value) -> {
//Get the value for key in map.
ArrayList<String> list = map.get(key);
if (list == null) {
map.put(key,value);
}
else {
//Merge two list together
ArrayList<String> mergedValue = new ArrayList<>(value);
mergedValue.addAll(list);
map.put(key , mergedValue);
}
});
return map;
}
The original implementation doesn't create result like Map<String, List<Object>>, but Map<String, List<List<String>>>. You need additional Stream pipeline on it to produce Map<String, List<String>>.
Map<String, List<String>> result = new HashMap<String, List<String>>();
Map<String, List<String>> map1 = new HashMap<String, List<String>>();
Map<String, List<String>> map2 = new HashMap<String, List<String>>();
for(Map.Entry<String, List<String>> entry: map1.entrySet()) {
result.put(entry.getKey(), new ArrayList<>(entry.getValue());
}
for(Map.Entry<String, List<String>> entry: map2.entrySet()) {
if(result.contains(entry.getKey())){
result.get(entry.getKey()).addAll(entry.getValue());
} else {
result.put(entry.getKey(), new ArrayList<>(entry.getValue());
}
}
This solution creates independent result map without any reference to map1 and map2 lists.
Using StreamEx
Map<String, List<String>> mergedMap =
EntryStream.of(mapGlobal)
.append(EntryStream.of(mapAdded))
.toMap((v1, v2) -> {
List<String> combined = new ArrayList<>();
combined.addAll(v1);
combined.addAll(v2);
return combined;
});
If you have even more maps to merge just append to the stream
.append(EntryStream.of(mapAdded2))
.append(EntryStream.of(mapAdded3))
Here is the full code to Iterate Two HashMap which has values stored in the form of a list. Merging all the keys and values in first hashmap. Below is the example.
HashMap<String, List<String>> hmap1 = new HashMap<>();
List<String> list1 = new LinkedList<>();
list1.add("000");
list1.add("111");
List<String> list2 = new LinkedList<>();
list2.add("222");
list2.add("333");
hmap1.put("Competitor", list1);
hmap1.put("Contractor", list2);
// System.out.println(hmap1);
HashMap<String, List<String>> hmap2 = new HashMap<>();
List<String> list3 = new LinkedList<>();
list3.add("aaa");
list3.add("bbb");
List<String> list4 = new LinkedList<>();
list4.add("ccc");
list4.add("ddd");
hmap2.put("Competitor", list3);
hmap2.put("Contractor", list4);
//******* Java 8 Feature *****
hmap1.forEach((k, v) -> hmap2.merge(k, v, (v1, v2) -> {
List<String> li = new LinkedList<>(v1);
li.addAll(v2);
hmap2.put(k,li);
return new ArrayList<>(li);
}));
System.out.println(hmap2);
Output:
{Competitor=[aaa, bbb, 000, 111], Contractor=[ccc, ddd, 222, 333]}

Categories

Resources