Prasad
Prasad

Reputation: 408

How to use groupingBy on nested lists

I am facing a tricky situation where I have to use groupingBy on an object which has nested lists. I tried few things with map(), flatmap(), toMap() but not able to come up with a solution and just going in circles. Any help here from the stream experts very much appreciated. I need to implement 2 methods in my OrdersAnalyzer class. Here is how my objects look like:

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

  //getters, setters, equals, hashcode omitted for brevity
}
public class OrderLine {
  private final Product product;
  private final Integer quantity;

  //getters, setters, equals, hashcode omitted for brevity
}
public class Product {
  private final String name;
  private final BigDecimal price;

  //getters, setters, equals, hashcode omitted for brevity
}
public class OrdersAnalyzer {
    /**
     * Should return at most three most popular products. Most popular product is the product that have the most occurrences
     * in given orders (ignoring product quantity).
     * If two products have the same popularity, then products should be ordered by name
     *
     * @param orders orders stream
     * @return list with up to three most popular products
     */
    public List<Product> findThreeMostPopularProducts(Stream<Order> orders) {
      orders.forEach(order -> {
        order.getOrderLines().stream().collect(
          Collectors.groupingBy(OrderLine::getProduct, *What to add here?* )
        );
      });
    }

    /**
     * Should return the most valuable customer, that is the customer that has the highest value of all placed orders.
     * If two customers have the same orders value, then any of them should be returned.
     *
     * @param orders orders stream
     * @return Optional of most valuable customer
     */
    public Optional<Customer> findMostValuableCustomer(Stream<Order> orders) {
      orders.collect(
        Collectors.groupingBy(Order::getCustomer, *What to add here?*)
      );
    }
}

Upvotes: 4

Views: 328

Answers (1)

Naman
Naman

Reputation: 31868

findThreeMostPopularProducts

The API that you are looking to support needs you to create a frequency map of products to lookup as you find the top-N. In such cases Collectors.counting() would be a good downstream to make use of.

Note that it would provide you Map<Product, Long> productFrequency as a resultant, and then you would need to define a custom Comparator<Map.Entry<Product, Long>> aided with an additional check over the name comparison.

Furthermore, iterating over the entries of this map, while sorting and limiting to N elements shall get you closer to the answer that you are looking for.


findMostValuableCustomer

In this API, you are looking to compare the customers based on the highest values across their respective orders. So the grouping is sufficient with the default toList downstream.

It would aid you with Map<Customer, List<Order>>, amongst which you would need to find the entry with a max value of the price from all the orders of each customer.

So a comparator such as Comparator.comparing(e -> findHighestValueOrder(e.getValue()), where the e.getValue() is a List<Order> customerOrders would help you with the solution.

I would leave the implementation of findHighestValueOrder up to you, since if you know how to map and flatMap, you just need to find the max price amongst these orders or fallback to some default value such as BigDecimal.ZERO.

Upvotes: 6

Related Questions