简体   繁体   中英

Java 8 multilevel grouping and reducing

I have a scenario where I have a list of objects of a type and I need to create a list of another objects of another type from it.

Below is the code: I have a list of Employees and need to create list of EmployeeInfo from the first list. Note that Employee has an account attribute, and the EmployeeInfo has a list of accounts. In this case, the same employee can have multiple accounts, so in the resulting employeeinfo list, each info object will have a list of accounts. Here is how i have done it:

public class Employee {

private final int dept;

private final String name;

private final String city;

private final String account;

public Employee(int dept, String name, String city, String account) {
    this.dept = dept;
    this.name = name;
    this.city = city;
    this.account = account;
}

public int getDept() {
    return dept;
}

public String getName() {
    return name;
}

public String getCity() {
    return city;
}

public String getAccount() {
    return account;
}

}

public class EmployeeInfo {

private final String name;

private final List<String> accounts;

public EmployeeInfo(String name, List<String> accounts) {
    this.name = name;
    this.accounts = accounts;
}

public String getName() {
    return name;
}

public List<String> getAccounts() {
    return accounts;
}

public EmployeeInfo addToList(EmployeeInfo employeeInfo) {
    List<String> l = new ArrayList<>();
    l.addAll(this.getAccounts());
    l.addAll(employeeInfo.getAccounts());
    return new EmployeeInfo(employeeInfo.name, l);
}

}

Test class:

public static void main(String[] args){
    List<Employee> employees = new ArrayList<>();
    //tradeId, secPool, datasetid, restricValue
    employees.add(new Employee(1, "Mary", "Boston", "A1"));
    employees.add(new Employee(1, "Mary", "Boston", "A2"));
    employees.add(new Employee(1, "Alex", "NYC", ""));
    employees.add(new Employee(2, "Peter", "DC", ""));
    employees.add(new Employee(1, "Sophia", "DC", "A4"));

    TestEmployeeGrouping testEmployeeGrouping = new TestEmployeeGrouping();

    Map<Integer, List<EmployeeInfo>> result = new HashMap<>();
    Map<Integer, Map<String, List<Employee>>> map =  employees.stream().collect(groupingBy(Employee::getDept, groupingBy(testEmployeeGrouping::createKey)));
    map.forEach((integer, stringListMap) -> {
        List<EmployeeInfo> employeeInfos = createInfo(stringListMap);
        result.put(integer, employeeInfos);
    });


}

private static List<EmployeeInfo> createInfo(Map<String,List<Employee>> stringListMap) {
    List<EmployeeInfo> employeeInfos = new ArrayList<>();
    stringListMap.forEach((s, employees) -> {
        List<String> accounts = employees.stream().map(Employee::getAccount).collect(Collectors.toList());
        employeeInfos.add(new EmployeeInfo(employees.get(0).getName(), accounts));
    });
    return employeeInfos;
}


private String createKey(Employee employee) {
    return employee.getDept() + employee.getName();
}

While the above piece works fine, finally giving me a list of employeeinfo, grouped by dept, each with its list of accounts, I wanted to do it in amore funcitonal way like:

employees.stream().collect(groupingBy(Employee::getDept, groupingBy(testEmployeeGrouping::createKey, reducing(EmployeeInfo::addToList))));

The above line throws error: Incompatible types: T is not convertible to Employee. Can someone please help me figure out how to resolve this?

Thanks!

This seems a bit complicated, but nothing is impossible

First do the group by on dept and name into map key as dept and values as List

second you need to do inner groupBy by employee name

Then get the values from inner map and convert into employee info with list of accounts

  Map<Integer, List<EmployeeInfo>> empInfo = employees.stream().collect(Collectors.groupingBy(Employee::getDept)).entrySet()
                         .stream().collect(Collectors.toMap(Map.Entry::getKey, value->value.getValue().stream().collect(Collectors.groupingBy(Employee::getName)).values()
                                 .stream().map(emp->new EmployeeInfo(emp.stream().findFirst().get().getName(), emp.stream().map(Employee::getAccount).collect(Collectors.toList())))
                                 .collect(Collectors.toList())));

Output

{1=[EmployeeInfo [name=Alex, accounts=[]], EmployeeInfo [name=Sophia, accounts=[A4]], EmployeeInfo [name=Mary, accounts=[A1, A2]]], 
 2=[EmployeeInfo [name=Peter, accounts=[]]]}

is there any reason you made the name in EmployeeInfo final? if you can change that this solution will work

add these two methods to EmployeeInfo

public void setName(String name) {
    this.name = name;
}
public void AddAccount(String account) {
    this.accounts.add(account);
}

and then you can do this

Collector<Employee, EmployeeInfo, EmployeeInfo> empToInfo = Collector.of(
     () -> new EmployeeInfo("", new ArrayList<String>()),
            (info, e) -> { 
                info.AddAccount(e.getAccount());
                info.setName(e.getName());
                },
     (p1,p2) -> p1.addToList(p2));

Collector<Employee, ?, Collection<EmployeeInfo>> byName = collectingAndThen(groupingBy(Employee::getName, empToInfo), 
                  (Map<String, EmployeeInfo> finisher) -> {return finisher.values();});

Map<Integer, Collection<EmployeeInfo>> r2 = employees.stream().collect(groupingBy(Employee::getDept, byName));

if you want to keep The EmployeeInfo immutable, you can use reduction instead of collection and it will be like this

Map<Integer, Collection<EmployeeInfo>> result2 = employees.stream().collect(groupingBy(Employee::getDept,
             collectingAndThen(groupingBy(Employee::getName, reducing(new EmployeeInfo("", new ArrayList<String>()), 
                                                                      empl3 -> new EmployeeInfo(empl3.getName(),Arrays.asList(empl3.getAccount())), 
                                                                      (inf1, inf2) -> inf1.addToList(inf2))), 
                                finisher -> finisher.values()))); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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