[英]Java Streams - How to preserve the initial Order of elements while using Collector groupingBy()
[英]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
。
為了做到這一點,需要使用一種需要使用mapFactory
的toMap()
風格(它還需要一個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.