How to collect a map of a pojo id to a set of a property of a collection on the pojo

Tags: , ,



I’m trying to use streams to get a map of a pojo to a Set of items exposed by a property on the pojo. I realise that isn’t clear so I’ll show how I’ve done it without streams.

I have and enum of product categories, and enum of Products and a list of shops with the product like this

public enum Category {
    FRUIT, VEGITABLE, MEAT;
}

public enum Product {
    APPLE ( Category.FRUIT ),
    PEAR ( Category.FRUIT ),
    BANANA ( Category.FRUIT ),
    CARROT ( Category.VEGITABLE ),
    POTATO ( Category.VEGITABLE ),
    CABBAGE ( Category.VEGITABLE ),
    MINCE ( Category.MEAT ),
    CHOP ( Category.MEAT ),
    STEAK ( Category.MEAT );

    private final Category category;

    Product( Category category ) {
        this.category = category;
    }

    public String value() {
        return name();
    }

    public static Product fromValue( String v ) {
        return valueOf( v );
    }

    public Category getCategory() {
        return this.category;
    }
}

public class Shop {

    private long id;
    private String name;
    private EnumSet<Product> products;

    public Shop( long id, String name, Collection<Product> products ) {
        this.id = id;
        this.name = name;
        this.products = ofNullable( products ).map( EnumSet::copyOf ).orElse( null );
    }
}

I have made a method which creates some dummy data…

public static List<Shop> getListOfShops() {
        Shop shop1 = new Shop( 1, "Green Grocer", EnumSet.of( Product.APPLE, Product.BANANA, Product.CARROT, Product.CABBAGE ) );
        Shop shop2 = new Shop( 2, "Supermarket", EnumSet.of( Product.APPLE, Product.BANANA
            , Product.CARROT, Product.CABBAGE, Product.CHOP, Product.MINCE ) );
        Shop shop3 = new Shop( 3, "Butcher", EnumSet.of( Product.STEAK ) );

        return Arrays.asList( shop1, shop2, shop3 );
    }

I want to create a map of shop categories

Map<String, EnumSet<Category>> shopCategories = new HashMap<>();

which will have contents like this

{Butcher=[MEAT], Supermarket=[FRUIT, VEGITABLE, MEAT], Green Grocer=[FRUIT, VEGITABLE]}

I have achieved this with some basic loops which I was hoping I was smart enough to convert into streams

public static Map<String, EnumSet<Category>> getCategories( List<Shop> shops ) {

    Map<String, EnumSet<Category>> shopCategories = new HashMap<>();

    for ( Shop s : shops ) {
        Set<Category> cats = new HashSet<Category>();
        for ( Product p : s.getProducts() ) {
            cats.add( p.getCategory() );
        }
        shopCategories.put ( s.getName(), EnumSet.copyOf (cats) );
    }

    return shopCategories;
}

I have a vague idea how to convert it to a map of shop to products with a collector as the terminal operation. Something along the lines of

shops.stream().collect( Collectors.toMap ( s -> s::getName ,
    PaymentProductWithCapabilities::getProducts ) );

I’m struggling for an entry point into understanding how to proceed to mapping the Categories instead ? Can anyone point me in the right direction ?

Answer

You could transform the products of the shop you are streaming on, into the value of the expected Mapusing the valueMapper in Collectors.toMap, similar to your iterative style as:

return shops.stream()
        .collect(Collectors.toMap(Shop::getName,
                s -> EnumSet.copyOf(s.getProducts().stream()
                        .map(Product::getCategory)
                        .collect(Collectors.toSet()))));

To be precise, your implementation overrides the value of the Map, if the same name is found for two shops, which in the Collectors.toMap can be implemented with the merge function such as (a,b) -> b.

Or better as suggested by Gene in a comments:

return shops.stream()
        .collect(Collectors.toMap(Shop::getName,
                s -> s.getProducts().stream()
                        .map(Product::getCategory)
                        .collect(Collectors.toCollection(
                                () -> EnumSet.noneOf(Category.class))),
                (a, b) -> b));


Source: stackoverflow