提问人:Mc_Android 提问时间:9/20/2023 最后编辑:Mark RotteveelMc_Android 更新时间:9/21/2023 访问量:119
如何按持续时间对对象列表进行分组和缩减
How to group and reduce a list of objects with duration
问:
我想创建一个按持续时间排序和汇总的人员列表。LocalDate
String
我做了一些关于在流中使用持续时间的研究:
以及使用流对对象列表进行分组并将对象列表简化为子组:
但是,我不可能解决我的问题,类是这样的:Person
class Person {
private LocalDate startDate;
private String personType;
private Duration time1;
private Duration time2;//constructor, getter+setter, other methods
}
创建的列表示例如下所示:
List<Person> personList = Arrays.asList(
new Person("2023-02-02","member1","08:00","4:00"),
new Person("2023-02-02","member1","50:00","0:45"),
new Person("2023-02-02","member2","10:00","0:40"),
new Person("2023-02-02","member2","01:00","1:20"),
new Person("2023-02-03","member1","08:00","2:00"),
new Person("2023-02-03","member1","10:00","0:45"),
new Person("2023-02-03","member2","10:00","1:40"),
new Person("2023-02-03","member2","02:00","1:20"),//...
);
我想创建一个按 startdate 和 personType 排序的人员列表,以及持续时间的总和。
期望输出:
("2023-02-02","member1","58:00","4:45"),
("2023-02-02","member2","11:00","2:00"),
("2023-02-03","member1","18:00","2:45"),
("2023-02-03","member2","12:00","3:00"),
...
我的方法是使用这样的东西。但是,我无法映射持续时间和字符串值:
Map<LocalDate,List<Person>> result=personList.stream()
.collect(Collectors.groupingBy(Person::getstartDate))
.entrySet().stream()
.collect(Collectors.toMap(x -> {
//how to sum duration values in here?
// Duration duration1 = x.getValue().stream() ...;
//how to use String values in here?
// String string = x.getValue().stream()....
return new Person(x.getKey(), string, duration1,duration2);
}, Map.Entry::getValue));
答:
1赞
Nikos Paraskevopoulos
9/20/2023
#1
一种方法是使用级联分组和减少以求和持续时间。以下结果将生成一个 2 级映射,其中包含每个日期和类型的总和:
Map<LocalDate, Map<String, Optional<Person>>> perDateAndTypeAggregated =
personList.stream().collect(Collectors.groupingBy(
Person::getStartDate,
Collectors.groupingBy(
Person::getType,
Collectors.reducing((p1, p2) ->
// this is where we sum the durations
new Person(p1.getStartDate(), p1.getType(), p1.getTime1().plus(p2.getTime1()), p1.getTime2().plus(p2.getTime2()))
)
)
));
如果您想要一个列表(地图提供了更丰富的信息,但这是您的应用程序),您可以按如下方式对上一个地图进行展平和排序:
Comparator<Person> byDateThenType =
Comparator.comparing(Person::getStartDate).thenComparing(Person::getType);
List<Person> result =
perDateAndTypeAggregated.values().stream()
.flatMap(m -> m.values().stream())
.filter(Optional::isPresent)
.map(Optional::get)
.sorted(byDateThenType)
.toList();
编辑:根据 Holger 的评论,可以使用 / 组合来简化上述操作,并通过删除组合来简化第二个操作 ():toMap
groupingBy
reducing
toList
.filter().map()
Map<LocalDate, Map<String, Person>> perDateAndTypeAggregated2 =
personList.stream().collect(Collectors.groupingBy(
Person::getStartDate,
Collectors.toMap(
Person::getType,
Function.identity(),
(p1, p2) ->
// this is where we sum the durations
new Person(p1.getStartDate(), p1.getType(), p1.getTime1().plus(p2.getTime1()), p1.getTime2().plus(p2.getTime2()))
)
));
List<Person> result2 =
perDateAndTypeAggregated2.values().stream()
.flatMap(m -> m.values().stream())
.sorted(byDateThenType)
.toList();
编辑2:我使用了以下类:Person
public static class Person {
private LocalDate startDate;
private String type;
private Duration time1;
private Duration time2;
public Person() {}
public Person(String startDate, String type, String time1, String time2) {
this.startDate = LocalDate.parse(startDate);
this.type = type;
this.time1 = Duration.parse(time1);
this.time2 = Duration.parse(time2);
}
public Person(LocalDate startDate, String type, Duration time1, Duration time2) {
this.startDate = startDate;
this.type = type;
this.time1 = time1;
this.time2 = time2;
}
// getters and setters...
@Override
public String toString() {
return "Person(" + startDate + "," + type + "," + time1 + "," + time2 + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(startDate, person.startDate) && Objects.equals(type, person.type) && Objects.equals(time1, person.time1) && Objects.equals(time2, person.time2);
}
@Override
public int hashCode() {
return Objects.hash(startDate, type, time1, time2);
}
}
并将列表初始化为:
List<Person> personList = Arrays.asList(
new Person("2023-02-02","member1","PT8H00M","PT4H00M"),
new Person("2023-02-02","member1","PT50H00M","PT0H45M"),
new Person("2023-02-02","member2","PT10H00M","PT0H40M"),
new Person("2023-02-02","member2","PT1H00M","PT1H20M"),
new Person("2023-02-03","member1","PT8H00M","PT2H00M"),
new Person("2023-02-03","member1","PT10H00M","PT0H45M"),
new Person("2023-02-03","member2","PT10H00M","PT1H40M"),
new Person("2023-02-03","member2","PT2H00M","PT1H20M")
);
所有这些代码都为我提供了问题所需的结果。
评论
1赞
Holger
9/20/2023
当您使用而不是 + 时,您不需要这些步骤。toMap
groupingBy
reducing
.filter(Optional::isPresent) .map(Optional::get)
0赞
Mc_Android
9/20/2023
谢谢你的建议。看来我必须花更多的时间来了解java stream.api的可能性
0赞
Nikos Paraskevopoulos
9/20/2023
@Holger 过滤器/映射步骤很丑陋,但这是使用简单形式的 的结果。此 IMO 使分组/减少步骤变得不那么复杂,但代价是更丑陋的排序/到列表步骤。如果我使用过滤器/映射将不再需要,但代价是分组/减少操作中的代码更复杂。我不确定会有什么帮助,但我对一个例子很感兴趣。reducing(BinaryOperator)
reducing(T identity, BinaryOperator<T> op)
toMap
1赞
Holger
9/20/2023
与其简单地使用合并功能,不如使用与减少功能相同的合并功能。然后,结果将是 而不是 .另请参阅替换 groupingBy 和 reduce by toMapCollectors.groupingBy(Person::getType, Collectors.reducing((p1, p2) -> …))
Collectors.toMap(Person::getType, Function.identity(), (p1, p2) -> …)
Map<String, Person>
Map<String, Optional<Person>>
0赞
Nikos Paraskevopoulos
9/21/2023
嘿@Holger是的,好主意,谢谢!我以为你的意思是替换第一个——这让我感到困惑;正如您在评论中明确指出的那样,替换第二个是完全有道理的。我会把它添加到答案中!groupingBy
评论
Map
Person
new Item(…)
toMap
PnYnMnDTnHnMnS
PT8H
08:00