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 ?
Advertisement
Answer
You could transform the products of the shop you are streaming on, into the value of the expected Map
using 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));