How to reduce a list of nested objects into a Map

Viewed 480

I have this problem that I need to solve using Java 8 streams, but so far I have not succeeded.

I have:

public class Customer {
   String id;
   String firstName;
   String lastName;
}

public class Order {
   private final String id;
   Set<OrderLine> orderLines = new HashSet<>();
   Customer customer;
}

public class OrderLine {
   Product product;
   Integer quantity;
}

public class Product {
   String name;
   BigDecimal price;
}

All objects have their own equals implemented so its possible to compare objects.

This is the data that i have to iterate from:

    List<Order> orders = new ArrayList<>();
    Customer customer1 = new Customer(randomId(), "Will", "Sanders");
    Customer customer2 = new Customer(randomId(), "Jonh", "Doe");
    Product p1 = new Product("Book 1", new BigDecimal("50.00"));
    Product p2 = new Product("Book 2", new BigDecimal("19.00"));
    Product p3 = new Product("Book 3", new BigDecimal("100.00"));
    orders.add(new Order(randomId(), customer1).addOrderLine(p1, 1));
    orders.add(new Order(randomId(), customer1).addOrderLine(p2, 1));
    orders.add(new Order(randomId(), customer2).addOrderLine(p3, 1));

And i want to know which customer has given more money to the company.

I have solved this using a foreach like this:

    orders.forEach(order -> {
        
        order.getOrderLines().forEach(orderLine -> {        
            
            if(!map.containsKey(order.getCustomer())) {
                map.put(order.getCustomer(), orderLine.getProduct().getPrice());
            } else {
                map.put(order.getCustomer(),map.get(order.getCustomer()).add(orderLine.getProduct().getPrice()));
            }
        });
    });
            
    Customer c = Collections.max(map.entrySet(), Comparator.comparing(Map.Entry::getValue)).getKey();

but i would like to know a way to solve this with streams.

My first thought was transforming this a Map<Customer,BigDecimal>, where that BigDecimal value would be the sum of all customer purchases.

Any Sugestions?

Thanks.

1 Answers

Let's do it step by step.

  1. How can you get the price of an order line?
BigDecimal getPrice(OrderLine orderLine) {
    return orderLine.getProduct()
                    .getPrice()
                    .multiply(BigDecimal.valueOf(orderLine.getQuantity()));
}
  1. How can you get the total price of an order?
BigDecimal getPrice(Order order) {
    return order.getOrderLines()
                .stream()
                .map(orderLine -> getPrice(orderLine))
                .collect(reducing(BigDecimal.ZERO, BigDecimal::add));
}
  1. Now assuming you have implemented the methods above, how can you determine the total money spent per each customer?
Map<Customer, BigDecimal> totalPerCustomer =
    orders.stream()
          .collect(groupingBy(Order::getCustomer,
                              reducing(BigDecimal.ZERO, o -> getPrice(o), BigDecimal::add)));
  1. Now as you have all the information stored in a convenient map, how can you determine which customer spent the most money?
Optional<Customer> topCustomer = 
    totalPerCustomer.entrySet()
                    .stream()
                    .max(Map.Entry.comparingByValue())
                    .map(Map.Entry::getKey);

Note that topCustomer will be equal to Optional.empty() if the list of orders is empty.

Related