簡體   English   中英

如何在應用嵌套分組收集器時保留所有子組

[英]How to preserve all Subgroups while applying nested groupingBy collector

我正在嘗試按性別部門對員工列表進行分組。

我如何確保所有部門都包含在每個性別排序順序中,即使相關的性別計數為零

目前,我有以下代碼和 output

employeeRepository.findAll().stream()
            .collect(Collectors.groupingBy(Employee::getGender, 
                        Collectors.groupingBy(Employee::getDepartment, 
                                              Collectors.counting())));

//output
//{MALE={HR=1, IT=1}, FEMALE={MGMT=1}}

首選output是:

{MALE={HR=1, IT=1, MGMT=0}, FEMALE={HR=0, IT=0, MGMT=1}}

要做到這一點,首先你必須按部門分組,然后才按性別分組,而不是相反。

第一個收集器groupingBy(Employee::getDepartment, _downstream_ )將根據部門將數據集拆分成組。 由於下游收集器partitioningBy(employee -> employee.getGender() == Employee.Gender.MALE, _downstream_ )將被應用,它將根據員工性別將映射到每個部門的數據分為部分。 最后,作為下游應用的Collectors.counting()將提供每個部門每個性別的員工總數

因此,由collect()操作生成的中間值 map將是Map<String, Map<Boolean, Long>>類型 - 每個部門員工性別計數 ( Boolean )(為簡單起見,部門是一個純字符串)。

下一步將這個 map 轉換為Map<Employee.Gender, Map<String, Long>> - employee count by department for each gender

我的方法是在條目集上創建一個 stream 並用一個新條目替換每個條目,它將以性別,為了保留有關部門的信息,它的反過來將是一個部門為的條目一個鍵和一個按部門計數的值。

然后通過entry key收集stream條groupingBy的條目。 應用mapping作為下游收集器來提取嵌套條目 然后應用Collectors.toMap()將類型為Map.Entry<String, Long>的條目收集到 map 中。

所有部門都包含在排序順序中

為確保嵌套 map(按計數划分的部門)中的順序,應使用NavigableMap

為了做到這一點,需要使用一種需要使用mapFactorytoMap()風格(它還需要一個mergeFunction ,它對這個任務並不是很有用,因為不會有重復項,但也必須提供它).

public static void main(String[] args) {
    List<Employee> employeeRepository = 
            List.of(new Employee("IT", Employee.Gender.MALE),
                    new Employee("HR", Employee.Gender.MALE),
                    new Employee("MGMT", Employee.Gender.FEMALE));

    Map<Employee.Gender, NavigableMap<String, Long>> departmentCountByGender = employeeRepository
            .stream()
            .collect(Collectors.groupingBy(Employee::getDepartment, // Map<String, Map<Boolean, Long>> - department to *employee count* by gender
                        Collectors.partitioningBy(employee -> employee.getGender() == Employee.Gender.MALE,
                                                  Collectors.counting())))
            .entrySet().stream()
            .flatMap(entryDep -> entryDep.getValue().entrySet().stream()
                    .map(entryGen -> Map.entry(entryGen.getKey() ? Employee.Gender.MALE : Employee.Gender.FEMALE,
                                               Map.entry(entryDep.getKey(), entryGen.getValue()))))
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                        Collectors.mapping(Map.Entry::getValue,
                                Collectors.toMap(Map.Entry::getKey,
                                                 Map.Entry::getValue,
                                                 (v1, v2) -> v1,
                                                 TreeMap::new))));

    System.out.println(departmentCountByGender);
}

用於演示目的的虛擬Employee class

class Employee {
    enum Gender {FEMALE, MALE};

    private String department;
    private Gender gender;
    // etc.
    
    // constructor, getters
}

Output

{FEMALE={HR=0, IT=0, MGMT=1}, MALE={HR=1, IT=1, MGMT=0}}

您可以繼續處理代碼的結果:

List<String> deptList = employees.stream().map(Employee::getDepartment).sorted().toList();

Map<Gender, Map<String, Long>> tmpResult = employees.stream()
        .collect(Collectors.groupingBy(Employee::getGender, Collectors.groupingBy(Employee::getDepartment, Collectors.counting())));

Map<Gender, Map<String, Long>> finalResult = new HashMap<>();

for (Map.Entry<Gender, Map<String, Long>> entry : tmpResult.entrySet()) {
    Map<String, Long> val = new LinkedHashMap<>();
    for (String dept : deptList) {
        val.put(dept, entry.getValue().getOrDefault(dept, 0L));
    }

    finalResult.put(entry.getKey(), val);
}

System.out.print(finalResult);

如果你想用一行代碼達到結果,代碼的可讀性或可維護性可能不會很好。

但是,如果您不介意使用第三方庫,還有一種選擇: abacus-common

Map<Gender, Map<String, Integer>> result = Stream.of(employees)
        .groupByToEntry(Employee::getGender, MoreCollectors.countingIntBy(Employee::getDepartment)) // step 1) group by gender 
        .mapValue(it -> Maps.newMap(deptList, Fn.identity(), dept -> it.getOrDefault(dept, 0), IntFunctions.ofLinkedHashMap())) // step 2) process the value.
        .toMap();

聲明:我是abacus-common的開發者

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM