How do I merge 2 HashMaps together? [duplicate] - java

This question already has answers here:
Merging 2 HashMaps in Java
(3 answers)
Closed 3 years ago.
I have 2 HashMaps of the type HashMap<String, Integer>. I would like to add them together in such a way that the values of duplicate keys get added together, rather than overwritten. This is the main reason why I can't use the putAll method for HashMaps. Is there a particular way I could do this easily?

You can use Map#merge e.g.
Map<String, Integer> map1 = new HashMap<>();
Map<String, Integer> map2 = new HashMap<>();
map1.put("a", 1);
map2.put("a", 2);
Map<String, Integer> map3 = new HashMap<>(map1);
map2.forEach((key, value) -> map3.merge(key, value, (v1,v2) -> v1+v2));
System.out.println(map3); // a=3

You can just use Stream.concat() to concatenate the streams of the two maps. Then you can collect them summing the duplicates:
Map<String, Integer> map1 = new HashMap<>();
map1.put("a", 2);
map1.put("b", 3);
Map<String, Integer> map2 = new HashMap<>();
map2.put("b", 1);
map2.put("c", 3);
Map<String, Integer> merged = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a + b));
Instead of (a, b) -> a + b you also can use Integer::sum.
The result for this would be:
{a=2, b=4, c=3}

You can use map merge from Java 8 :
public static void main(String[] args) {
Map<String, Integer> map1 = new HashMap<>();
map1.put("1", 1);
map1.put("2", 2);
Map<String, Integer> map2 = new HashMap<>();
map2.put("2", 2);
map2.put("3", 3);
map1.forEach((key, value) -> map2.merge(key, value, Integer::sum));
map2.forEach((s, integer) -> System.out.println(s + " " + integer));
}
Output is :
1 1
2 4
3 3

When it comes to making simple one-liners, the Stream API is your friend. I would use the Stream#collect method and the Collectors#toMap methods. For example:
Map<String, Integer> map1 = new HashMap<String, Integer>() {
{
put("first", 1);
put("second", 4);
put("third", 7);
}
};
Map<String, Integer> map2 = new HashMap<String, Integer>() {
{
put("fourth", 5);
put("fifth", 9);
put("third", 3);
}
};
Map<String, Integer> result = Stream.of(map1, map2).map(Map::entrySet).flatMap(Collection::stream)
.collect(Collectors.<Entry<String, Integer>, String, Integer>toMap(Entry::getKey, Entry::getValue,
(t, u) -> t + u));

Related

Java 8 - List<Map> into Single Map [duplicate]

This question already has answers here:
Convert List of Maps to single Map via streams
(4 answers)
convert list of map to map using flatMap
(3 answers)
Create a map from a list of maps
(2 answers)
Closed 2 years ago.
I have a List<Map<String, String>>. I would like to convert it into single map.
The list size can be 1..n. Not sure how to do that using Java 8 Stream?
List<Map<String, String>> mapList = new ArrayList<>();
Map<String, String> map1 = new HashMap<>();
map1.put("1", "One");
map1.put("2", "Two");
Map<String, String> map2 = new HashMap<>();
map2.put("3", "Three");
map2.put("4", "Four");
mapList.add(map1);
mapList.add(map2);
Map<String, String> finalMap = new HashMap<>();
We could do something like this in Java 7:
for(Map<String, String> map : mapList) {
for(String key : map.keySet()) {
finalMap.put(key, map.get(key));
}
}
System.out.println("Final Map " + finalMap); // Final Map {1=One, 2=Two, 3=Three, 4=Four}
You can flatMap it and then use Collectors.toMap:
mapList.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Note, that duplicate keys are not handled. To ignore duplicates, you can use:
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)
Here is one way to do it. I choose a method that handles duplicates by putting them in a list of strings. I added duplicate of each map in the other to demonstrate.
List<Map<String, String>> mapList = new ArrayList<>();
Map<String, String> map1 = new HashMap<>();
map1.put("1", "One");
map1.put("2", "Two");
map1.put("4", "Four");
Map<String, String> map2 = new HashMap<>();
map2.put("3", "Three");
map2.put("1", "One");
map2.put("4", "Four");
mapList.add(map1);
mapList.add(map2);
Map<String,List<String>> combined = mapList.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Entry::getKey,
Collectors.mapping(Entry::getValue,
Collectors.toList())));
combined.entrySet().forEach(System.out::println);
Prints
1=[One, One]
2=[Two]
3=[Three]
4=[Four, Four]

Collect Map<String, Integer> from stream of Map<String, Map<String, Integer>>

I have a nested map with key as Employee name and values as another map with key as company name and value as years of experience like below
Map<String, Map<String, Integer>> map = new HashMap<>();
Map<String, Integer> innerMap1 = new HashMap<>();
innerMap1.put("INfosys", 2);
innerMap1.put("Volvo", 2);
innerMap1.put("MH", 3);
innerMap1.put("Piterion", 1);
Map<String, Integer> innerMap2 = new HashMap<>();
innerMap2.put("Tata", 2);
innerMap2.put("Bosch", 1);
innerMap2.put("Amber", 1);
innerMap2.put("E2", 1);
map.put("Rahul", innerMap1);
map.put("Amrita", innerMap2);
Now my function should return a Map with the employee name as key and total experience as value. How can I do that using java streams (in a single stream)
public Map<String, Integer> getEmployeesWithExp(Map<String, Map<String, Integer>> map) {
map.entrySet().stream().
...
return null;
}
There probably are multiple ways but you could collect the entries into a new map and reduce the values of the inner maps to integers, e.g. like this:
Map<String, Integer> result =
map.entrySet().stream()
.collect(
Collectors.toMap(e -> e.getKey(), //or Map.Entry::getKey
e -> e.getValue().values().stream()
.reduce(0, Integer::sum)));
This is the first time I tried to use streams with maps, it was quite a good exerxcise, thanks.
I failed to do it in only one stream, though. This solution features one main stream and internal streams.
I used org.apache.commons.lang3.tuple.Pair, by the way.
Map<String, Integer> result = map.entrySet().stream()
.map(entry -> Pair.of(entry.getKey(), entry.getValue().values().stream().reduce(0, Integer::sum)))
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
It answered
"Amrita" → 5
"Rahul" → 8
I believe it is correct. :D
This is simple for loops used for solution :-
Map<String, Integer> finalMap = new HashMap<>();
for (Entry<String, Map<String, Integer>> entry : map.entrySet()) {
Integer exp = 0;
for (Entry<String, Integer> entry2 : entry.getValue().entrySet()) {
exp += entry2.getValue();
}
finalMap.put(entry.getKey(), exp);
}
Output- {Amrita=5, Rahul=8}
Can be done in simple way:-
Function< Map<String, Integer>,Integer> sumOfValue = (x) -> x.values().stream().mapToInt(Integer::intValue).sum();
stringMapMap.entrySet().stream().collect(Collectors.toMap(
e-> e.getKey(), e-> sumOfValue.apply(e.getValue())
));

Compute the Mean of a List<Double> in a HashMap in Java

Given a map from Names to lists of Numbers.
I'd like to compute the mean for each Name using the java 8 stream api.
Map<String, List<Double>> NameToQuaters = new HashMap<>();
Map<String, Double> NameToMean = ?
You need something like this :
Map<String, Double> nameToMean = nameToQuaters.entrySet()
.stream()
.collect(Collectors.toMap(
// the key is the same
Map.Entry::getKey,
// for the value of the key, you can calculate the average like so
e -> e.getValue().stream().mapToDouble(Double::doubleValue).average().getAsDouble())
);
}
Or you can create a method which make the average and return it back for example :
public Double average(List<Double> values) {
return values.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
}
then your code can be :
Map<String, Double> nameToMean = nameToQuaters.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> average(e.getValue())) );
This should do the trick:
Map<String, List<Double>> nameToQuaters = new HashMap<>();
//fill source map
Map<String, Double> nameToMean = new HashMap<>();
nameToQuaters.
.forEach((key, value) -> nameToMean.put(key, value.stream().mapToDouble(a -> a).average().getAsDouble()));

How to filter Array of Map with multiple value using Guava

I have an Array containing Map. And I want to filter my array using some (multiple) key and value inside of the map object. For example, WHERE ID > 1 AND Name <> "cc" (key > 1, Name<>"cc").
How can i do that in Java?
I have imported the Guava libraries that has Collections2 to filter the array.
But, I didn't found any example that is filtering Map object inside the array.
here is some of my example codes:
List<Map<String, Object>> baseList = new ArrayList<>();
Map<String, Object> map1 = new HashMap<>();
map1.put("ID", 1);
map1.put("Name", "aa");
baseList.add(map1);
Map<String, Object> map2 = new HashMap<>();
map2.put("ID", 2);
map2.put("Name", "bb");
baseList.add(map2);
Map<String, Object> map3 = new HashMap<>();
map3.put("ID", 3);
map3.put("Name", "cc");
baseList.add(map3);
List<Map<String, Object>> filteredList = new ArrayList<>();
filteredList = Collections2.filter() ???
I want to filter with a kind of ID >= 1 AND NAME<>"cc" Which will resulting Array containing Map object like this: [{ID=1,Name="aa"}, {ID=2,Name="bb"}]
Anyone can help?
I have no idea what do you need Guava for. I'd do that in the following way:
List<Map<String, Object>> filteredList = baseList.stream()
.filter(map -> map.entrySet().stream()
.anyMatch(e -> e.getKey().equals(1L) && e.getValue().equals("cc")))
.collect(Collectors.toList());
Do you use Java 8? You can do:
List<Map<String, Object>> filteredList = maps.stream()
.filter(map -> (Integer) map.get("ID") >= 1 && !"cc".equals(map.get("Name")))
.collect(Collectors.toList());
to have new list with filtered maps.
If you want collection view using Guava goodies (or no Java 8), you should use Collections2.filter:
Collection<Map<String, Object>> filteredList = Collections2.filter(
maps, new Predicate<Map<String, Object>>() {
#Override
public boolean apply(#Nullable Map<String, Object> map) {
return (Integer) map.get("ID") >= 1 && !"cc".equals(map.get("Name"));
}
});
there's no Lists.filter, see IdeaGraveyard for explanation, hence only Collection interface is provided.
Do you really need list of maps instead of Map<Integer, String> (or maybe Map<Integer, YourDomainObject>)? Then you could do:
final Map<Integer, String> map = ImmutableMap.of(
1, "aa",
2, "bb",
3, "cc");
final Map<Integer, String> filteredMap = Maps.filterEntries(map,
e -> e.getKey() >= 1 && !"cc".equals(e.getValue()));

How can I combine two HashMap objects containing the same types?

I have two HashMap objects defined like so:
HashMap<String, Integer> map1 = new HashMap<String, Integer>();
HashMap<String, Integer> map2 = new HashMap<String, Integer>();
I also have a third HashMap object:
HashMap<String, Integer> map3;
How can I merge map1 and map2 together into map3?
map3 = new HashMap<>();
map3.putAll(map1);
map3.putAll(map2);
If you know you don't have duplicate keys, or you want values in map2 to overwrite values from map1 for duplicate keys, you can just write
map3 = new HashMap<>(map1);
map3.putAll(map2);
If you need more control over how values are combined, you can use Map.merge, added in Java 8, which uses a user-provided BiFunction to merge values for duplicate keys. merge operates on individual keys and values, so you'll need to use a loop or Map.forEach. Here we concatenate strings for duplicate keys:
map3 = new HashMap<>(map1);
for (Map.Entry<String, String> e : map2.entrySet())
map3.merge(e.getKey(), e.getValue(), String::concat);
//or instead of the above loop
map2.forEach((k, v) -> map3.merge(k, v, String::concat));
If you know you don't have duplicate keys and want to enforce it, you can use a merge function that throws an AssertionError:
map2.forEach((k, v) ->
map3.merge(k, v, (v1, v2) ->
{throw new AssertionError("duplicate values for key: "+k);}));
Taking a step back from this specific question, the Java 8 streams library provides toMap and groupingBy Collectors. If you're repeatedly merging maps in a loop, you may be able to restructure your computation to use streams, which can both clarify your code and enable easy parallelism using a parallel stream and concurrent collector.
One-liner using Java 8 Stream API:
map3 = Stream.of(map1, map2).flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
Among the benefits of this method is ability to pass a merge function, which will deal with values that have the same key, for example:
map3 = Stream.of(map1, map2).flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, Math::max))
Java 8 alternative one-liner for merging two maps:
defaultMap.forEach((k, v) -> destMap.putIfAbsent(k, v));
The same with method reference:
defaultMap.forEach(destMap::putIfAbsent);
Or idemponent for original maps solution with third map:
Map<String, Integer> map3 = new HashMap<String, Integer>(map2);
map1.forEach(map3::putIfAbsent);
And here is a way to merge two maps into fast immutable one with Guava that does least possible intermediate copy operations:
ImmutableMap.Builder<String, Integer> builder = ImmutableMap.<String, Integer>builder();
builder.putAll(map1);
map2.forEach((k, v) -> {if (!map1.containsKey(k)) builder.put(k, v);});
ImmutableMap<String, Integer> map3 = builder.build();
See also Merge two maps with Java 8 for cases when values present in both maps need to be combined with mapping function.
If you don't need mutability for your final map, there is Guava's ImmutableMap with its Builder and putAll method which, in contrast to Java's Map interface method, can be chained.
Example of use:
Map<String, Integer> mergeMyTwoMaps(Map<String, Integer> map1, Map<String, Integer> map2) {
return ImmutableMap.<String, Integer>builder()
.putAll(map1)
.putAll(map2)
.build();
}
Of course, this method can be more generic, use varargs and loop to putAll Maps from arguments etc. but I wanted to show a concept.
Also, ImmutableMap and its Builder have few limitations (or maybe features?):
they are null hostile (throw NullPointerException - if any key or value in map is null)
Builder don't accept duplicate keys (throws IllegalArgumentException if duplicate keys were added).
HashMap has a putAll method.
http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html
You could use Collection.addAll() for other types, e.g. List, Set, etc. For Map, you can use putAll.
Generic solution for combining two maps which can possibly share common keys:
In-place:
public static <K, V> void mergeInPlace(Map<K, V> map1, Map<K, V> map2,
BinaryOperator<V> combiner) {
map2.forEach((k, v) -> map1.merge(k, v, combiner::apply));
}
Returning a new map:
public static <K, V> Map<K, V> merge(Map<K, V> map1, Map<K, V> map2,
BinaryOperator<V> combiner) {
Map<K, V> map3 = new HashMap<>(map1);
map2.forEach((k, v) -> map3.merge(k, v, combiner::apply));
return map3;
}
Very late but let me share what I did when I had the same issue.
Map<String, List<String>> map1 = new HashMap<>();
map1.put("India", Arrays.asList("Virat", "Mahi", "Rohit"));
map1.put("NZ", Arrays.asList("P1","P2","P3"));
Map<String, List<String>> map2 = new HashMap<>();
map2.put("India", Arrays.asList("Virat", "Mahi", "Rohit"));
map2.put("NZ", Arrays.asList("P1","P2","P4"));
Map<String, List<String>> collect4 = Stream.of(map1, map2)
.flatMap(map -> map.entrySet().stream())
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(strings, strings2) -> {
List<String> newList = new ArrayList<>();
newList.addAll(strings);
newList.addAll(strings2);
return newList;
}
)
);
collect4.forEach((s, strings) -> System.out.println(s+"->"+strings));
It gives the following output
NZ->[P1, P2, P3, P1, P2, P4]
India->[Virat, Mahi, Rohit, Virat, Mahi, Rohit]
A small snippet I use very often to create map from other maps:
static public <K, V> Map<K, V> merge(Map<K, V>... args) {
final Map<K, V> buffer = new HashMap<>();
for (Map m : args) {
buffer.putAll(m);
}
return buffer;
}
you can use HashMap<String, List<Integer>> to merge both hashmaps and avoid losing elements paired with the same key.
HashMap<String, Integer> map1 = new HashMap<>();
HashMap<String, Integer> map2 = new HashMap<>();
map1.put("key1", 1);
map1.put("key2", 2);
map1.put("key3", 3);
map2.put("key1", 4);
map2.put("key2", 5);
map2.put("key3", 6);
HashMap<String, List<Integer>> map3 = new HashMap<>();
map1.forEach((str, num) -> map3.put(str, new ArrayList<>(Arrays.asList(num))));
//checking for each key if its already in the map, and if so, you just add the integer to the list paired with this key
for (Map.Entry<String, Integer> entry : map2.entrySet()) {
Integer value = entry.getValue();
String key = entry.getKey();
if (map3.containsKey(key)) {
map3.get(key).add(value);
} else {
map3.put(key, new ArrayList<>(Arrays.asList(value)));
}
}
map3.forEach((str, list) -> System.out.println("{" + str + ": " + list + "}"));
output:
{key1: [1, 4]}
{key2: [2, 5]}
{key3: [3, 6]}
You can use putAll function for Map as explained in the code below
HashMap<String, Integer> map1 = new HashMap<String, Integer>();
map1.put("a", 1);
map1.put("b", 2);
map1.put("c", 3);
HashMap<String, Integer> map2 = new HashMap<String, Integer>();
map1.put("aa", 11);
map1.put("bb", 12);
HashMap<String, Integer> map3 = new HashMap<String, Integer>();
map3.putAll(map1);
map3.putAll(map2);
map3.keySet().stream().forEach(System.out::println);
map3.values().stream().forEach(System.out::println);
Below snippet takes more than one map and combine them.
private static <K, V> Map<K, V> combineMaps(Map<K, V>... maps) {
if (maps == null || maps.length == 0) {
return Collections.EMPTY_MAP;
}
Map<K, V> result = new HashMap<>();
for (Map<K, V> map : maps) {
result.putAll(map);
}
return result;
}
Demo example link.
Assuming the following input:
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Map;
...
var m1 = Map.of("k1", 1, "k2", 2);
var m2 = Map.of("k3", 3, "k4", 4);
When you're sure not to have any key collisions between both input maps, a simple expression that avoids any mutations and yields an immutable result could be:
var merged = Stream.concat(
m1.entrySet().stream(),
m2.entrySet().stream()
).collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
In case key collisions are possible, we can provide a lambda to specify how to de-duplicate them. For example if we'd like to keep the largest value in case an entry is present in both input, we could:
.collect(Collectors.toUnmodifiableMap(
Map.Entry::getKey,
Map.Entry::getValue,
Math::max)) // any function (Integer, Integer) -> Integer is ok here
HashMap<Integer,String> hs1 = new HashMap<>();
hs1.put(1,"ram");
hs1.put(2,"sita");
hs1.put(3,"laxman");
hs1.put(4,"hanuman");
hs1.put(5,"geeta");
HashMap<Integer,String> hs2 = new HashMap<>();
hs2.put(5,"rat");
hs2.put(6,"lion");
hs2.put(7,"tiger");
hs2.put(8,"fish");
hs2.put(9,"hen");
HashMap<Integer,String> hs3 = new HashMap<>();//Map is which we add
hs3.putAll(hs1);
hs3.putAll(hs2);
System.out.println(" hs1 : " + hs1);
System.out.println(" hs2 : " + hs2);
System.out.println(" hs3 : " + hs3);
Duplicate items will not be added(that is duplicate keys) as when we will print hs3 we will get only one value for key 5 which will be last value added and it will be rat.
**[Set has a property of not allowing the duplicate key but values can be duplicate]
Method 1: Put maps in a List and then join
public class Test15 {
public static void main(String[] args) {
Map<String, List<String>> map1 = new HashMap<>();
map1.put("London", Arrays.asList("A", "B", "C"));
map1.put("Wales", Arrays.asList("P1", "P2", "P3"));
Map<String, List<String>> map2 = new HashMap<>();
map2.put("Calcutta", Arrays.asList("Protijayi", "Gina", "Gini"));
map2.put("London", Arrays.asList( "P4", "P5", "P6"));
map2.put("Wales", Arrays.asList( "P111", "P5555", "P677666"));
System.out.println(map1);System.out.println(map2);
// put the maps in an ArrayList
List<Map<String, List<String>>> maplist = new ArrayList<Map<String,List<String>>>();
maplist.add(map1);
maplist.add(map2);
/*
<T,K,U> Collector<T,?,Map<K,U>> toMap(
Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
*/
Map<String, List<String>> collect = maplist.stream()
.flatMap(ch -> ch.entrySet().stream())
.collect(
Collectors.toMap(
//keyMapper,
Entry::getKey,
//valueMapper
Entry::getValue,
// mergeFunction
(list_a,list_b) -> Stream.concat(list_a.stream(), list_b.stream()).collect(Collectors.toList())
));
System.out.println("Final Result(Map after join) => " + collect);
/*
{Wales=[P1, P2, P3], London=[A, B, C]}
{Calcutta=[Protijayi, Gina, Gini], Wales=[P111, P5555, P677666], London=[P4, P5, P6]}
Final Result(Map after join) => {Calcutta=[Protijayi, Gina, Gini], Wales=[P1, P2, P3, P111, P5555, P677666], London=[A, B, C, P4, P5, P6]}
*/
}//main
}
Method 2 : Normal Map merge
public class Test15 {
public static void main(String[] args) {
Map<String, List<String>> map1 = new HashMap<>();
map1.put("London", Arrays.asList("A", "B", "C"));
map1.put("Wales", Arrays.asList("P1", "P2", "P3"));
Map<String, List<String>> map2 = new HashMap<>();
map2.put("Calcutta", Arrays.asList("Protijayi", "Gina", "Gini"));
map2.put("London", Arrays.asList( "P4", "P5", "P6"));
map2.put("Wales", Arrays.asList( "P111", "P5555", "P677666"));
System.out.println(map1);System.out.println(map2);
/*
<T,K,U> Collector<T,?,Map<K,U>> toMap(
Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
*/
Map<String, List<String>> collect = Stream.of(map1,map2)
.flatMap(ch -> ch.entrySet().stream())
.collect(
Collectors.toMap(
//keyMapper,
Entry::getKey,
//valueMapper
Entry::getValue,
// mergeFunction
(list_a,list_b) -> Stream.concat(list_a.stream(), list_b.stream()).collect(Collectors.toList())
));
System.out.println("Final Result(Map after join) => " + collect);
/*
{Wales=[P1, P2, P3], London=[A, B, C]}
{Calcutta=[Protijayi, Gina, Gini], Wales=[P111, P5555, P677666], London=[P4, P5, P6]}
Final Result(Map after join) => {Calcutta=[Protijayi, Gina, Gini], Wales=[P1, P2, P3, P111, P5555, P677666], London=[A, B, C, P4, P5, P6]}
*/
}//main
}
In Python, HashMap is called Dictionary and we can merge them very easily.
x = {'Roopa': 1, 'Tabu': 2}
y = {'Roopi': 3, 'Soudipta': 4}
z = {**x,**y}
print(z)
{'Roopa': 1, 'Tabu': 2, 'Roopi': 3, 'Soudipta': 4}
you can use - addAll method
http://download.oracle.com/javase/6/docs/api/java/util/HashMap.html
But there is always this issue that - if your two hash maps have any key same - then it will override the value of the key from first hash map with the value of the key from second hash map.
For being on safer side - change the key values - you can use prefix or suffix on the keys - ( different prefix/suffix for first hash map and different prefix/suffix for second hash map )

Categories