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

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

Related

How do I merge 2 HashMaps together? [duplicate]

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

Sort the values (Set -> SortedSet) of the Map with Java 8 Streams

How to sort values of the Map<String, Set<String>> i.e. convert to Map<String, SortedSet<String>> with streams?
Just iterate over each entry and convert the Set<T> (e.g. HashSet<T>) to a SortedSet<T> (e.g. TreeSet<T>) as:
Map<String, Set<String>> input = new HashMap<>();
Map<String, SortedSet<String>> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, new TreeSet<>(v)));
or with streams as:
Map<String, Set<String>> input = new HashMap<>();
Map<String, SortedSet<String>> output = input.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, a -> new TreeSet<>(a.getValue())));

How to group nested maps

My current attempt:
Map<Integer, Map<String, Integer>> collect = shopping.entrySet()
.stream()
.collect(Collectors.toMap/*groupingBy? */(e -> e.getKey().getAge(),
e -> e.getValue().entrySet().stream().collect(Collectors.groupingBy(b -> b.getKey().getCategory(), Collectors.summingInt(Map.Entry::getValue)))));
shopping is basically a map: Map<Client, Map<Product,Integer>>,
The problem comes from the fact that the provided data contains multiple values by key - there are Clients with same ages, and the code works only for a single value by key.
How could I make this code work also for multiple keys?
I suppose it should be somehow changed to use collect collect(Collectors.groupingBy) ->
in the resulting map Map<Integer, Map<String, Integer>>:
The outer key (Integer) represents the client age.
The inner key (String) - represents product category
The inner maps value (Integer) - represents the number of products
which belong to a specific category.
My attempt using groupingBy:
Map<Integer, Map<String, Integer>> collect = shopping.entrySet()
.stream()
.collect(Collectors.groupingBy(/*...*/))
Simply I want to refactor that code into one using streams:
Map<Integer, Map<String, Integer>> counts = new HashMap<>();
for (Map.Entry<Client, Map<Product, Integer>> iData : shopping.entrySet()) {
int age = iData.getKey().getAge();
for (Map.Entry<Product, Integer> iEntry : iData.getValue().entrySet()) {
String productCategory = iEntry.getKey().getCategory();
counts.computeIfAbsent(age, (agekey) -> new HashMap<>()).compute(productCategory, (productkey, value) -> value == null ? 1 : value + 1);
}
}
A non-stream(forEach) way to convert your for loop could be :
Map<Integer, Map<String, Integer>> counts = new HashMap<>();
shopping.forEach((key, value1) -> value1.keySet().forEach(product ->
counts.computeIfAbsent(key.getAge(),
(ageKey) -> new HashMap<>())
.merge(product.getCategory(), 1, Integer::sum)));
This would be more appropriate via a groupingBy collector instead of toMap.
Map<Integer, Map<String, Integer>> result = shopping.entrySet()
.stream()
.collect(groupingBy(e -> e.getKey().getAge(),
flatMapping(e -> e.getValue().keySet().stream(),
groupingBy(Product::getCategory,
summingInt(e -> 1)))));
note this uses flatMapping which is only available in the standard library as of jdk9.

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

Best way to find element in List Java [duplicate]

It's a simple question,
I have a simple HashMap of which i want to reverse the keys and values.
HashMap<Character, String> myHashMap = new HashMap<Character, String>();
myHashMap.put('a', "test one");
myHashMap.put('b', "test two");
and I want to create a new HashMap in which i put the opposites.
HashMap<String, Character> reversedHashMap = new HashMap<String, Character>();
e.g. Keys "test one" & "test two" and values 'a' & 'b'.
They all are unique, yes
If you're sure that your values are unique you can iterate over the entries of your old map .
Map<String, Character> myNewHashMap = new HashMap<>();
for(Map.Entry<Character, String> entry : myHashMap.entrySet()){
myNewHashMap.put(entry.getValue(), entry.getKey());
}
Alternatively, you can use a Bi-Directional map like Guava provides and use the inverse() method :
BiMap<Character, String> myBiMap = HashBiMap.create();
myBiMap.put('a', "test one");
myBiMap.put('b', "test two");
BiMap<String, Character> myBiMapInversed = myBiMap.inverse();
As java-8 is out, you can also do it this way :
Map<String, Integer> map = new HashMap<>();
map.put("a",1);
map.put("b",2);
Map<Integer, String> mapInversed =
map.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey))
Finally, I added my contribution to the proton pack library, which contains utility methods for the Stream API. With that you could do it like this:
Map<Character, String> mapInversed = MapStream.of(map).inverseMapping().collect();
Apache commons collections library provides a utility method for inversing the map. You can use this if you are sure that the values of myHashMap are unique
org.apache.commons.collections.MapUtils.invertMap(java.util.Map map)
Sample code
HashMap<String, Character> reversedHashMap = MapUtils.invertMap(myHashMap)
If the values are not unique, the safe way to inverse the map is by using java 8's groupingBy function
Map<String, Integer> map = new HashMap<>();
map.put("a",1);
map.put("b",2);
Map<Integer, List<String>> mapInversed =
map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList())))
I wrote a simpler loop that works too (note that all my values are unique):
HashMap<Character, String> myHashMap = new HashMap<Character, String>();
HashMap<String, Character> reversedHashMap = new HashMap<String, Character>();
for (char i : myHashMap.keySet()) {
reversedHashMap.put(myHashMap.get(i), i);
}
To answer your question on how you can do it, you could get the entrySet from your map and then just put into the new map by using getValue as key and getKey as value.
But remember that keys in a Map are unique, which means if you have one value with two different key in your original map, only the second key (in iteration order) will be kep as value in the new map.
Iterate through the list of keys and values, then add them.
HashMap<String, Character> reversedHashMap = new HashMap<String, Character>();
for (String key : myHashMap.keySet()){
reversedHashMap.put(myHashMap.get(key), key);
}
private <A, B> Map<B, A> invertMap(Map<A, B> map) {
Map<B, A> reverseMap = new HashMap<>();
for (Map.Entry<A, B> entry : map.entrySet()) {
reverseMap.put(entry.getValue(), entry.getKey());
}
return reverseMap;
}
It's important to remember that put replaces the value when called with the same key. So if you map has two keys with the same value only one of them will exist in the inverted map.
Tested with below sample snippet, tried with MapUtils, and Java8 Stream feature. It worked with both cases.
public static void main(String[] args) {
Map<String, String> test = new HashMap<String, String>();
test.put("a", "1");
test.put("d", "1");
test.put("b", "2");
test.put("c", "3");
test.put("d", "4");
test.put("d", "41");
System.out.println(test);
Map<String, String> test1 = MapUtils.invertMap(test);
System.out.println(test1);
Map<String, String> mapInversed =
test.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
System.out.println(mapInversed);
}
Output:
{a=1, b=2, c=3, d=41}
{1=a, 2=b, 3=c, 41=d}
{1=a, 2=b, 3=c, 41=d}
Use forEach introduced in Java 8
Map<Short, String> regularMap = new HashMap<>();
Map<String, Short> inversedMap = new HashMap<>();
regularMap.forEach((key, value) -> inversedMap.put(value, key));
for reverting the map, in your case:
private void reverseMap(Map<Character, String> map) {
Map<String, Character> newList = new HashMap<>();
map.forEach((key, value) -> newList.put(value, key));
System.out.println(newList);
}
or you can traverse the old hashmap
HashMap<String, Character> newList = new HashMap<String, Character>();
for (String key : list.keySet()){
newList.put(list.get(key), key);
}
For Reversing the Array of Dictionary. (If values are Unique)
private void reverseArrayMap(List<Map<String, String>> list) {
// reversing the array of dictionary
List<Map<String, String>> newList = new ArrayList<>();
Map<String, String> resDic = new HashMap<>();
for (Map<String, String> map : list) {
map.forEach((key, value) -> resDic.put(value, key));
newList.add(resDic);
}
System.out.println("Original Array of Dictionary" + list);
System.out.println("Reversed Array of Dictionary" + newList);
}
Java :
Simple approach, No need for java 8
Map<String,String> map=new HashMap<>();
Map<String,String> mapInv=new HashMap<>();
for (String key : map.keySet())
mapInv.put(map.get(key), key);
Java 8:
forEach() is a new method to iterate the elements. It is defined in Iterable and Stream interface.
Map<String,String> map=new HashMap<>();
Map<String,String> mapInv=new HashMap<>();
map.forEach((key, value) -> mapInv.put(value, key));
Kotlin :
val map: Map<String, String> = HashMap()
val mapInv: MutableMap<String?, String> = HashMap()
for (key in map.keys) mapInv[map[key]] = key

Categories