英文:
How to collect a map of a pojo id to a set of a property of a collection on the pojo
问题
我正在尝试使用流来将POJO映射到POJO上的属性公开的项目集合。我意识到这不够清楚,所以我将展示如何在没有流的情况下完成它。
我有一个产品类别的枚举,一个产品的枚举以及一个包含产品的商店列表,如下所示:
public enum Category {
    FRUIT, VEGETABLE, MEAT;
}
public enum Product {
    APPLE(Category.FRUIT),
    PEAR(Category.FRUIT),
    BANANA(Category.FRUIT),
    CARROT(Category.VEGETABLE),
    POTATO(Category.VEGETABLE),
    CABBAGE(Category.VEGETABLE),
    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 = Optional.ofNullable(products).map(EnumSet::copyOf).orElse(null);
    }
}
我创建了一个方法来创建一些虚拟数据:
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);
}
我想要创建一个商店类别的映射:
Map<String, EnumSet<Category>> shopCategories = new HashMap<>();
其中内容如下:
{Butcher=[MEAT], Supermarket=[FRUIT, VEGETABLE, MEAT], Green Grocer=[FRUIT, VEGETABLE]}
我已经使用一些基本的循环实现了这一点,但我希望能够将其转换为流。
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<>();
        for (Product p : s.getProducts()) {
            cats.add(p.getCategory());
        }
        shopCategories.put(s.getName(), EnumSet.copyOf(cats));
    }
    return shopCategories;
}
我有一个模糊的想法如何将其转换为商店到产品的映射,使用终端操作进行收集。大致如下:
shops.stream().collect(Collectors.toMap(s -> s::getName, PaymentProductWithCapabilities::getProducts));
我在理解如何继续映射类别方面遇到了困难。有人可以指点我正确的方向吗?
英文:
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 ?
答案1
得分: 2
你可以使用Collectors.toMap中的valueMapper将你正在流式传输的商店的产品转换为预期的Map的值,就像你的迭代风格一样:
return shops.stream()
        .collect(Collectors.toMap(Shop::getName,
                s -> EnumSet.copyOf(s.getProducts().stream()
                        .map(Product::getCategory)
                        .collect(Collectors.toSet()))));
要精确一点,你的实现会覆盖Map中相同名称的两个商店的值,可以在Collectors.toMap中使用合并函数来实现,例如(a, b) -> b。
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));
英文:
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));
答案2
得分: 1
同样的结果可以使用Collectors.groupingBy和Collectors.flatMapping来实现 -
Map<String, EnumSet<Category>> categories =
        shops.stream()
            .collect(
                Collectors.groupingBy(
                    Shop::getName,
                    Collectors.flatMapping(
                        s -> s.getProducts().stream().map(Product::getCategory),
                        Collectors.toCollection(() -> EnumSet.noneOf(Category.class)))));
Collectors.groupingBy 根据 Shop::getName 对流进行分组,而 Collectors.flatMapping 则将产品列表扁平化,并使用 map 将产品映射到 Product 的类别。Collectors.flatMapping 还有一个 Collector<? super U, A, R> downstream 参数,可以用来收集 EnumSet<Category>。
英文:
Same can be achieved using Collectors.groupingBy and Collectors.flatMapping as -
Map<String, EnumSet<Category>> categories =
        shops.stream()
            .collect(
                groupingBy(
                    Shop::getName,
                    flatMapping(
                        s -> s.getProducts().stream().map(Product::getCategory),
                        toCollection(() -> EnumSet.noneOf(Category.class)))));
Collectors.groupingBy groups stream based on Shop::getName and Collectors.flatMappingflattens List of Products and map maps product to the Product category. Collectors.flatMapping also Collector<? super U, A, R> downstream which can be used to collect EnumSet<Category>.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论