简体   繁体   English

Java Stream 在单循环中以声明方式分别按多个字段分组

[英]Java Stream Grouping by multiple fields individually in declarative way in single loop

I googled for it but I mostly found cases for grouping by aggregated fields or on to alter response of stream but not the scenario below:我搜索了它,但我主要发现了按聚合字段分组或更改 stream 响应的案例,但不是以下场景:

I have a class User with fields category and marketingChannel .我有一个 class User字段categorymarketingChannel

I have to write a method in the declarative style that accepts a list of users and counts users based on category and also based on marketingChannel individually (ie not groupingBy(... ,groupingBy(..)) ).我必须编写一个声明式的方法,该方法接受用户列表并根据categorymarketingChannel单独计算用户(即不是groupingBy(... ,groupingBy(..)) )。

I am unable to do it in a single loop.我无法在一个循环中做到这一点。 This is what I have to achieve.这是我必须达到的。

I coded few methods as follows:我编写了一些方法如下:

import java.util.*;
import java.util.stream.*;
public class Main
{
    public static void main(String[] args) {
        List<User> users = User.createDemoList();
        imperative(users);
        declerativeMultipleLoop(users);
        declerativeMultipleColumn(users);
    }
    
    public static void imperative(List<User> users){
        Map<String, Integer> categoryMap = new HashMap<>();
        Map<String, Integer> channelMap = new HashMap<>();
        for(User user : users){
           Integer  value = categoryMap.getOrDefault(user.getCategory(), 0);
           categoryMap.put(user.getCategory(), value+1);
           value = channelMap.getOrDefault(user.getMarketingChannel(), 0);
           channelMap.put(user.getMarketingChannel(), value+1);
        }
        System.out.println("imperative");
        System.out.println(categoryMap);
        System.out.println(channelMap);
    }
    
    public static void declerativeMultipleLoop(List<User> users){
        Map<String, Long> categoryMap = users.stream()
        .collect(Collectors.groupingBy(
            User::getCategory, Collectors.counting()));
        Map<String, Long> channelMap = users.stream()
        .collect(Collectors.groupingBy(
            User::getMarketingChannel, Collectors.counting()));
        System.out.println("declerativeMultipleLoop");
        System.out.println(categoryMap);
        System.out.println(channelMap);
    }
    
    public static void declerativeMultipleColumn(List<User> users){
        Map<String, Map<String, Long>> map = users.stream()
        .collect(Collectors.groupingBy(
            User::getCategory,
            Collectors.groupingBy(User::getMarketingChannel, 
            Collectors.counting())));
       
        System.out.println("declerativeMultipleColumn");
        System.out.println("groupingBy category and marketChannel");
        System.out.println(map);
        
        Map<String, Long> categoryMap = new HashMap<>();
        Map<String, Long> channelMap = new HashMap<>();
        
        for (Map.Entry<String, Map<String, Long>> entry: map.entrySet()) {
            String category = entry.getKey();
            Integer count = entry.getValue().size();
            Long value = categoryMap.getOrDefault(category,0L);
            categoryMap.put(category, value+count);
            for (Map.Entry<String, Long> channelEntry : entry.getValue().entrySet()){
                String channel = channelEntry.getKey();
                Long channelCount = channelEntry.getValue();
                Long channelValue = channelMap.getOrDefault(channel,0L);
                channelMap.put(channel, channelValue+channelCount);
            }
        }
        System.out.println("After Implerative Loop on above.");
        System.out.println(categoryMap);
        System.out.println(channelMap);
    }
}
class User{
    private String name;
    private String category;
    private String marketChannel;
    
    public User(String name, String category, String marketChannel){
        this.name = name;
        this.category = category;
        this.marketChannel = marketChannel;
    }
    public String getName(){
        return this.name;
    }
    public String getCategory(){
        return this.category;
    }
    public String getMarketingChannel(){
        return this.marketChannel;
    }
    
     @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name) &&
                Objects.equals(category, user.category) &&
                Objects.equals(marketChannel, user.marketChannel);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, category, marketChannel);
    }
    public static List<User> createDemoList(){
        return Arrays.asList(
            new User("a", "student","google"),
            new User("b", "student","bing"),
            new User("c", "business","google"),
            new User("d", "business", "direct")
            );
    }

The method declerativeMultipleLoop is declarative but it has a separate loop for each field. declerativeMultipleLoop方法是声明性的,但它对每个字段都有一个单独的循环。 Complexity: O(noOfFields * No of users)复杂性:O(noOfFields * 用户数)

The problem is in declerativeMultipleColumn Method as I end up writing imperative code and multiple loops.问题出在declerativeMultipleColumn方法中,因为我最终编写了命令式代码和多个循环。

I want to write the above method in completely declarative and as efficient as possible.我想以完全声明性和尽可能高效的方式编写上述方法。 ie Complexity: O(No of users)即复杂性:O(用户数)

Sample output:样品 output:

imperative至关重要的
{business=2, student=2} {企业=2,学生=2}
{direct=1, google=2, bing=1} {直接=1,谷歌=2,必应=1}
declerativeMultipleLoop declerativeMultipleLoop
{business=2, student=2} {企业=2,学生=2}
{direct=1, google=2, bing=1} {直接=1,谷歌=2,必应=1}
declerativeMultipleColumn declerativeMultipleColumn
groupingBy category and marketChannel按类别和市场渠道分组
{business={direct=1, google=1}, student={google=1, bing=1}} {企业={direct=1, google=1}, 学生={google=1, bing=1}}
After Implerative Loop on above.在上面的祈使循环之后。
{business=2, student=2} {企业=2,学生=2}
{direct=1, google=2, bing=1} {直接=1,谷歌=2,必应=1}

If I understand your requirement it is to use a single stream operation that results in 2 separate maps.如果我了解您的要求,那就是使用单个 stream 操作,从而产生 2 个单独的地图。 That is going to require a structure to hold the maps and a collector to build the structure.这将需要一个结构来保存地图和一个收集器来构建结构。 Something like the following:类似于以下内容:

class Counts {
    public final Map<String, Integer> categoryCounts = new HashMap<>();
    public final Map<String, Integer> channelCounts = new HashMap<>();

    public static Collector<User,Counts,Counts> countsCollector() {
        return Collector.of(Counts::new, Counts::accept, Counts::combine, CONCURRENT, UNORDERED);
    }

    private Counts() { }

    private void accept(User user) {
        categoryCounts.merge(user.getCategory(), 1, Integer::sum);
        channelCounts.merge(user.getChannel(), 1, Integer::sum);
    }

    private Counts combine(Counts other) {
        other.categoryCounts.forEach((c, v) -> categoryCounts.merge(c, v, Integer::sum));
        other.channelCounts.forEach((c, v) -> channelCounts.merge(c, v, Integer::sum));
        return this;
    }
}

That can then be used as a collector:然后可以将其用作收集器:

Counts counts = users.stream().collect(Counts.countsCollector());
counts.categoryCounts.get("student")...

(Opinion only: the distinction between imperative and declarative is pretty arbitrary in this case. Defining stream operations feels pretty procedural to me (as opposed to the equivalent in, say, Haskell)). (仅供参考:在这种情况下,命令式和声明式之间的区别是相当随意的。定义 stream 操作对我来说感觉非常程序化(与 Haskell 中的等价物相反))。

You can compute two maps in a single forEach method:您可以在单个forEach方法中compute两个映射:

public static void main(String[] args) {
    List<User> users = Arrays.asList(
            new User("a", "student", "google"),
            new User("b", "student", "bing"),
            new User("c", "business", "google"),
            new User("d", "business", "direct"));
    Map<String, Integer> categoryMap = new HashMap<>();
    Map<String, Integer> channelMap = new HashMap<>();

    // group users into maps
    users.forEach(user -> {
        categoryMap.compute(user.getCategory(),
                (key, value) -> value == null ? 1 : value + 1);
        channelMap.compute(user.getChannel(),
                (key, value) -> value == null ? 1 : value + 1);
    });
    // output
    System.out.println(categoryMap); // {business=2, student=2}
    System.out.println(channelMap); // {direct=1, google=2, bing=1}
}
static class User {
    private final String name, category, channel;

    public User(String name, String category, String channel) {
        this.name = name;
        this.category = category;
        this.channel = channel;
    }

    public String getName() { return this.name; }
    public String getCategory() { return this.category; }
    public String getChannel() { return this.channel; }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM