ankit rai
ankit rai

Reputation: 334

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 - enter image description here

Upvotes: 2

Views: 4995

Answers (2)

Thiyagu
Thiyagu

Reputation: 17880

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

Upvotes: 3

Thiyagu
Thiyagu

Reputation: 17880

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

  1. If the mapping does not exist, it adds it to the map (result map)
  2. 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())));

Upvotes: 2

Related Questions