Java8 HashMap inside another Hashmap, how to find salary sum - java

I have a structure as shown below. I am trying to calculate the sum of all employee salaries. I would like to use Java streams. Could someone please explain how I can achieve this?
Employee has a getSalary method.
Map<String, Map<String, Employee>> mainMap = new HashMap<>();
Map<String, Employee> emplmap1 = new HashMap<>();
Map<String, Employee> emplmap2 = new HashMap<>();
emplmap1.put("A",empl1);
emplmap1.put("B",empl2);

You want to use flatMap, which can accept an element that's a stream and then flattens all the streams into one stream, and a map which converts elements in a stream to different elements:
double salary = mainMap.values().stream()
.flatMap(m -> m.values().stream())
.map(Employee::getSalary)
.mapToDouble(Double::doubleValue)
.sum();
System.out.println("Total salary: " + salary);
mainMap.values().stream() will return a stream of mainMap's values (the maps). We flatMap the stream by turning every element (a map) to a stream of that map's values. Then we get the salaries, turn them into primitive doubles and finally, we sum them.

You might want to consider using the built in stats collector if you will possibly need other stats in the future.
DoubleSummaryStatistics salaryStats = mainMap.values().stream()
.flatMap(m -> m.values().stream())
.collect(Collectors.summarizingDouble(Employee::getSalary));
That way you get sum, count, max, min, average.

Related

Java 8 Stream Create an Object from Map of Objects

I just started with learning and implementing collections via the Java 8 stream API. I have one class:
public class Discount {
int amount;
String lastMarketingRegion;
Discount (int amount, String lastMarketingRegion) {
this.amount = amount;
this.lastMarketingRegion= lastMarketingRegion;
}
public int getAmount() { return amount; }
public String getLastMarketingRegion() { return lastMarketingRegion; }
public String toString() {
return String.format("{%s,\"%s\"}", amount, lastMarketingRegion);
}
}
And I am given with the following:
Map<String, Discount> prepaid = new HashMap<String, Discount>();
prepaid.put("HAPPY50", new Discount(100, "M1"));
prepaid.put("LUCKY10", new Discount(10, "M2"));
prepaid.put("FIRSTPAY", new Discount(20, "M3"));
Map<String, Discount> otherBills = new HashMap<String, Discount>();
otherBills.put("HAPPY50", new Discount(60, "M4"));
otherBills.put("LUCKY10", new Discount(7, "M5"));
otherBills.put("GOOD", new Discount(20, "M6"));
List<Map<String, Discount>> discList = new ArrayList<Map<String, Discount>>();
discList.add(prepaid);
discList.add(otherBills);
So, basically I have a list of Discount maps of all discount codes for different payment types.
Requirement is to create a single map with all the discount codes across all payment types with sum_of_amount and the last_region:
Map<String, Discount> totalDiscounts =
{LUCKY10={17, "M5"}, FIRSTPAY={20, "M3"}, HAPPY50={160, "M4"}, GOOD={20, "M6"}}
I am able to get:
Map<String, Integer> totalDiscounts =
{LUCKY10=17, FIRSTPAY=20, HAPPY50=160, GOOD=20}
by using the following code:
Map<String, Integer> afterFormatting = discList.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.summingInt(map -> map.getValue().amount)));
but I need a Discount object also with the region.
I need a collection of Discount objects where the amount is the total of the amounts of same key and region is from otherBills.
Any help would be much appreciated. Thank You.
Edit 1 -
For the sake of simplicity, please consider lastMarketingRegion to have same value for a discount code.
I also tried to explain it via diagram -
From comments
Why do you expect "LUCKY10" - "M5" when you have "M2" and "M5" entries for LUCKY10?
because otherBills has more priority than prepaid
You can use Collectors.toMap for this. The last argument to it is the mergeFunction that merges two Discounts that had same String key in the map.
Map<String, Discount> totalDiscounts = discList.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
(discount1, discount2) -> new Discount(discount1.getAmount() + discount2.getAmount(),
discount2.getLastMarketingRegion())));
Since the stream generated out of a list is ordered, the discount2 Discount will be the one from the otherBills map and hence I'm picking the region of it.
If you have constructed the list by adding otherBills followed by prepaid, then this will have a different output.
Relying on the encounter order makes this a not-a-great-solution.
(If you are going to assume we process entries from the second map after processing the first, why merge them in the first place?)
See my other answer that uses Map.merge
If you have just two maps, then rather than going for a stream-based solution (my other answer), you can use Map.merge for this.
Here, we make a copy of the prepaid map. Then we iterate through the otherBills map. For each key
If the mapping does not exist, it adds it to the map (result map)
If the mapping already exists, we construct a new Discount object whose amount is the sum of amounts of the Discount object already present in the map (the one from prepaid) and the current Discount object (the one from otherBill). It takes the region of the Discount object from the otherBill map.
Map<String, Discount> result = new HashMap<>(prepaid);
otherBills.forEach((k, v) -> result.merge(k, v, (discountFromPrepaid, discountFromOtherBill) ->
new Discount(discountFromPrepaid.getAmount() + discountFromOtherBill.getAmount(),
discountFromOtherBill.getLastMarketingRegion())));

How to iterate and work on the values of a map whose values are a list of elements using java 8 streams and lambdas

Gist: We are trying to rewrite our old java code with java 8 streams and lambdas wherever possible.
Question:
I have a map whose key is a string and values are list of user defined objects.
Map<String, List<Person>> personMap = new HashMap<String, List<Person>>();
personMap.put("A", Arrays.asList(person1, person2, person3, person4));
personMap.put("B", Arrays.asList(person5, person6, person7, person8));
Here the persons are grouped based on their name's starting character.
I want to find the average age of the persons on the list for every key in the map.
For the same, i have tried many things mentioned in stackoverflow but none is matching with my case or few are syntactically not working.
Thanks In Advance!
You didn't specify how (or if) you would like the ages to be stored, so I have it as just a print statement at the moment. Nevertheless, it's as simple as iterating over each entry in the Map and printing the average of each List (after mapping the Person objects to their age):
personMap.forEach((k, v) -> {
System.out.println("Average age for " + k + ": " + v.stream().mapToInt(Person::getAge).average().orElse(-1));
});
If you would like to store it within another Map, then you can collect it to one pretty easily:
personMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
.stream()
.mapToInt(Person::getAge)
.average()
.orElse(-1)));
Note: -1 is returned if no average is present, and this assumes a getter method exists within Person called getAge, returning an int.
You can do it like so:
// this will get the average over a list
Function<List<Person>, Double> avgFun = (personList) ->
personList.stream()
.mapToInt(Person::getAge)
.average()
.orElseGet(null);
// this will get the averages over your map
Map<String, Double> result = personMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> avgFun.apply(entry.getValue())
));

Java Stream or Map Merge by key

What would be the simplest way to merge Map key values like keys "55", "55004", "550009", "550012" into one key: "55" and a sum of all those values().
I'm trying to think of ways to use containsKey or trimming the key. It's very hard to think about this.
Maybe a flatMap to flatten the map and reduce.
#Test
public void TestM(){
Map<String,Object> map1 = new HashMap();
map1.put("55", 3453.34);
map1.put("55001", 5322.44);
map1.put("55003", 10112.44);
map1.put("55004", 15555.74);
map1.put("77", 1000.74); // instead of 1000 it should be ~1500
map1.put("77004", 444.74);
map1.put("77003", 66.74);
// in real example I'll need "77" and "88" and "101" etc.
// All of which has little pieces like 77004, 77006
Map<String,Double> SumMap = new HashMap<String, Double>();
SumMap = map1.entrySet().stream().map
(e->e.getValue()).reduce(0d, Double::sum);
// INCORRECT
// REDUCE INTO ONE KEY startsWith 55
System.out.println("Map: " + SumMap);
// RESULT should be :
// Map<String, Double> result = { "55": TOTAL }
// real example might be "77": TOTAL, "88": TOTAL, "101": TOTAL
//(reducing away the "77004", "88005" etc.)
}
Basically this code reduces and rolls subitem totals into a bigger key.
It looks like you could use Collectors.groupingBy.
It requires Function which would allow us decide which elements belong to same group. Such function for elements from same group should always return same value which will be used as key in resulting map. In your case it looks like you want to group elements with same first two characters stored in key, which suggest mapping to substring(0,2).
When we already have way to determine which elements belong to same group, we can now specify how we want map to collect them. By default it collects them in list so we have key->[elemnt0, element1, ...] mapping.
But we can specify your own way of handling elements from same group by providing our own Collector. Since we want to create sum of values we can use Collectors.summingDouble(mappingToDouble).
DEMO:
Map<String, Double> map1 = new HashMap<>();
map1.put("661", 123d);
map1.put("662", 321d);
map1.put("55", 3453.34);
map1.put("55001", 5322.44);
map1.put("55003", 10112.44);
map1.put("55004", 15555.74);
Map<String, Double> map = map1.entrySet()
.stream()
.collect(
Collectors.groupingBy(
entry -> entry.getKey().substring(0, 2),
Collectors.summingDouble(Map.Entry::getValue)
)
);
System.out.println(map);
Output: {66=444.0, 55=34443.96}

Transforming list values of a map to their mean value by an attribute

I start with Map<String,List<Rating>>. Rating has a method int getValue().
I want to end up with Map<String,Integer> where the Integer value is the mean value of all the Rating.getValue() values grouped by the key from the original Map<String,List<Rating>>.
I would be pleased to receive some ideas on how to tackle this.
Performing aggregation operations on a collection of integers can be done with IntStream methods. In your case, average seems like the right method to use (notice that it returns a Double, not Integer, which seems like a better choice).
What you want is to convert each entry of the original map to an entry in a new map, where the key remains the same, and the value is the average of the values of the List<Rating> elements. Generating the output map can be done using a toMap Collector.
Map<String,Double> means =
inputMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e->e.getValue()
.stream()
.mapToInt(Rating::getValue)
.average()
.orElse(0.0)));
It can be done using averagingInt as next:
Map<String, Double> means =
map.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream().collect(
Collectors.averagingInt(Rating::getValue)
)
)
);
Assuming that you would like to go a little bit further and you need more statistics like count, sum, min, max and average, you could consider using summarizingInt instead, you will then get IntSummaryStatistics instead of a Double
Map<String, IntSummaryStatistics> stats =
map.entrySet()
.stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream().collect(
Collectors.summarizingInt(Rating::getValue)
)
)
);

Cleaner map reduce with Java 8 and a HashMap

Right now I have a HashMap I'm trying to reduce to a total. Here's the current code:
HashMap map = new HashMap<String, Long>();
map.put("ex1", 757L);
map.put("ex2", 77L);
map.values()
.stream()
.reduce(0L, (a, b) -> a + b);
Is there a more elegant way to do this? I'd like to use the sum method.
You can use mapToLong to get a LongStream then call sum()
long sum = map.values().stream().mapToLong(i -> i).sum();

Categories

Resources