Search code examples
javajava-streamcollectorsgroupingby

Creating a Map<U, List<T>> from a List<T> where each T contains a List<U> using Stream API


I have a List of Products and every product contains a list of Ingredients.

public class Product {
    private String id;
    private List<Ingredient> ingredients;

    // other code
}

public class Ingredient {
    private String name;

    // other code
}

I want to collect products into Map grouping them by each ingredient.

How can I do it by using Stream API's collect() and Collectors.groupingBy()?

Product p1 = new Product("1", Arrays.asList(new Ingredient("i1"), new Ingredient("i2"), new Ingredient("i3")));
Product p2 = new Product("2", Arrays.asList(new Ingredient("i1"), new Ingredient("i3"), new Ingredient("i5")));
Product p3 = new Product("3", Arrays.asList(new Ingredient("i2"), new Ingredient("i4"), new Ingredient("i5")));

List<Product> products = List.of(p1, p2, p3);
    
Map<Ingredient, List<Product>> result = products.stream()
    .collect(Collectors.groupingBy( not_sure_what_needs_to_go_here ));

The expected result should look like:

[i1 : {p1, p2} ,
 i2 : {p1, p3} ,
 i3 : {p1, p2},
 i4 : {p3} ,
 i5 : {p2, p2}]

Solution

  • You can create a Tuple

    @Value
    static class Tuple<L, R> {
        L left;
        R right;
    }
    

    or a Java record

    public record IngProd (Ingredient ingredient, Product product) {}
    

    and then stream over your products flatmapping to a tuple and collect using grouping by

    Map<Ingredient, List<Product>> result =
            products.stream()
                    .flatMap(prod -> prod.getIngredients().stream().map(ing -> new Tuple<>(ing, prod)))
                    .collect(groupingBy(Tuple::getLeft, mapping(Tuple::getRight, toList())));
    

    when using a record change

    Tuple::getLeft & Tuple::getRight
    

    to

    IngProd::getIngredient & IngProd::getProduct