2

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

11
  • 3
    Why do you expect "LUCKY10" - "M5" when you have "M2" and "M5" entries for LUCKY10? Commented Sep 1, 2018 at 8:27
  • 2
    Your question is not clear. Please be more precise on your requirement. Explain it cleanly so that we can help you. Commented Sep 1, 2018 at 8:39
  • @daniu because otherBills has more priority than prepaid. Commented Sep 1, 2018 at 8:41
  • @ernest_k yes, I need a collection of Discount objects where the amount is the total of the amounts of same key and region is from otherBills. Commented Sep 1, 2018 at 8:42
  • @ankitrai please add the explanation you've given here into the question Commented Sep 1, 2018 at 8:43

2 Answers 2

3

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

Sign up to request clarification or add additional context in comments.

3 Comments

@nullpointer I have edited the answer stating the assumptions
@nullpointer My solution only picks M5 and not M2 since (2<5). I couldn't understand your point
Your solution worked. Thanks a lot for the help.. Time for me to probably get my hands dirtier in stram api.
2

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

1 Comment

IMHO, preferably better than creating a List<Map<K,V>>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.