Java Streams - group by two criteria summing result

J-Alex :

I have a list of Orders I should group by two criteria.

Order_Id| Customer |    Date    | Amount |
   1    | "Sam"    | 2019-03-21 | 100    |
   2    | "Nick"   | 2019-03-21 | 102    |
   3    | "Dan"    | 2019-03-21 | 300    |
   4    | "Sam"    | 2019-04-21 | 400    |
   5    | "Jenny"  | 2019-04-21 | 220    |
   6    | "Jenny"  | 2019-04-12 | 330    |

Top buyer for each month by summed amount should be found, for current example:

{
  MARCH: { customer='Dan', amount=300 }, 
  APRIL: { customer='Jenny', amount=550 }
}

There is a solution I was able to find:

public class Main {

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order(1L, "Sam", LocalDate.of(2019, 3, 21), 100L),
                new Order(2L, "Nick", LocalDate.of(2019, 3, 21), 102L),
                new Order(3L, "Dan", LocalDate.of(2019, 3, 21), 300L),
                new Order(4L, "Sam", LocalDate.of(2019, 4, 21), 400L),
                new Order(5L, "Jenny", LocalDate.of(2019, 4, 21), 220L),
                new Order(6L, "Jenny", LocalDate.of(2019, 4, 12), 330L)
        );

        solution1(orders);
    } 

    private static void solution1(List<Order> orders) {
        final Map<Month, Map<String, Long>> buyersSummed = new HashMap<>();

        for (Order order : orders) {
            Map<String, Long> customerAmountMap = buyersSummed.computeIfAbsent(order.getOrderMonth(), mapping -> new HashMap<>());
            customerAmountMap.putIfAbsent(order.getCustomer(), 0L);
            Long customerAmount = customerAmountMap.get(order.getCustomer());
            customerAmountMap.put(order.getCustomer(), customerAmount + order.getAmount());
        }

        final Map<Month, BuyerDetails> topBuyers = buyersSummed.entrySet().stream()
                .collect(
                        toMap(Entry::getKey, customerAmountEntry -> customerAmountEntry.getValue().entrySet().stream()
                                .map(entry -> new BuyerDetails(entry.getKey(), entry.getValue()))
                                .max(Comparator.comparingLong(BuyerDetails::getAmount)).orElseThrow())
                );

        System.out.println(topBuyers);
    }

}

The data model I used:

class BuyerDetails {
    String customer;
    Long amount;

    public BuyerDetails(String customer, Long amount) {
        this.customer = customer;
        this.amount = amount;
    }

    public String getCustomer() {
        return customer;
    }

    public Long getAmount() {
        return amount;
    }

}

class Order {

    Long id;
    String customer;
    LocalDate orderDate;
    Long amount;

    public Order(Long id, String customer, LocalDate orderDate, Long amount) {
        this.id = id;
        this.customer = customer;
        this.orderDate = orderDate;
        this.amount = amount;
    }

    public Long getId() {
        return id;
    }

    public String getCustomer() {
        return customer;
    }

    public LocalDate getOrderDate() {
        return orderDate;
    }

    public Month getOrderMonth() {
        return getOrderDate().getMonth();
    }

    public Long getAmount() {
        return amount;
    }
}

The question:

Is there any way to solve the task above in one stream?

Nidhish Krishnan :

Try using groupingBy, summingLong and comparingLong like as shown below

Map<Month, BuyerDetails> topBuyers = orders.stream()
    .collect(Collectors.groupingBy(Order::getOrderMonth,
             Collectors.groupingBy(Order::getCustomer,
             Collectors.summingLong(Order::getAmount))))
    .entrySet().stream()
    .collect(Collectors.toMap(Map.Entry::getKey,
             order -> order.getValue().entrySet().stream()
            .max(Comparator.comparingLong(Map.Entry::getValue))
            .map(cust -> new BuyerDetails(cust.getKey(), cust.getValue())).get()));

Output

{
  "MARCH": { "customer": "Dan", "amount": 300 }, 
  "APRIL": { "customer": "Jenny", "amount": 550 }
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=138617&siteId=1