如何解决我应该在单个循环中使用多个流吗?
想象一下我们正在使用这个类:
public Student{
String name;
Integer age;
Integer height;
Integer weight;
}
现在我们有一个学生名单,我们会被问到类似的问题:
- 过滤那些名字为“Jonh”的人并得到平均年龄
- 过滤那些名字是“玛丽”的人并获得最大的高度
- 过滤名称为“Ben”的那些并获得最小的权重
我认为一个简洁易懂的解决方案是使用 lambdas 按名称过滤并获取所需内容:
List<Student> students = ...
double jonhAge = students.stream().filter(s->s.getName().equals("Jonh").mapToInt(s->s.getAge()).average()
double maryHeight = students.stream().filter(s->s.getName().equals("Mary").mapToInt(s->s.getHeight()).max()
double benWeight = students.stream().filter(s->s.getName().equals("Ben").mapToInt(s->s.getWeight()).min()
我猜它至少会遍历列表 3 次,并且使用带有条件的单个循环可能更有效:
double jonhAge = 0;
double jonhCount = 0;
double maryHeight = 0;
double benWeight = 1000;
for(Student s : students){
if(s.getName.equals("Jonh")){
jonhAge += s.getAge();
jonhCount++;
}else if(s.getName.equals("Mary")){
if(s.getHeight()>maryHeight)
maryHeight = s.getHeight();
}else if(s.getName.equals("Ben")){
if(s.getWeight()<benWeight )
benWeight = s.getWeight();
}
}
jonhAge = jonhAge / jonhCount;
我认为第一种方式更简洁,但第二种方式更快。我对吗?我应该使用哪一种? 想象一下,学生列表包含大量元素,而且名字比 Jonh、Mary 和 Ben 还多。
解决方法
这可以通过以下方式使用 Stream API 实现:
- 创建名称到
Student
类的适当 getter 的映射 - 为
IntSummaryStatistics
类创建另一个名称到 getter 的映射 - 使用
Collectors.groupingBy
+Collectors.summarizingInt
构建地图以获取每个学生的统计数据,并使用Collectors.toMap
转换此地图以获取学生所需的统计参数。
更新
可以实现一个枚举来存储对 Student
和 IntSummaryStatistics
中适当的 getter 的引用,然后映射应该使用枚举值。
enum FieldStat {
AGE_AVERAGE(Student::getAge,IntSummaryStatistics::getAverage),HEIGHT_MAX(Student::getHeight,IntSummaryStatistics::getMax),WEIGHT_MIN(Student::getWeight,IntSummaryStatistics::getMin);
private final ToIntFunction<Student> field;
private final Function<IntSummaryStatistics,Number> stat;
FieldStat(ToIntFunction<Student> getter,Function<IntSummaryStatistics,Number> stat) {
this.field = getter;
this.stat = stat;
}
public ToIntFunction<Student> getField() {
return field;
}
public Function<IntSummaryStatistics,Number> getStat() {
return stat;
}
}
示例实现:
List<Student> students = Arrays.asList(
new Student("John",35,177,78),new Student("John",29,173,72),new Student("Mary",32,164,58),28,167,56),new Student("Ben",24,181,84),27,169,65),new Student("Bob",178,80)
);
Map<String,FieldStat> mapStat = Map.of(
"John",FieldStat.AGE_AVERAGE,"Mary",FieldStat.HEIGHT_MAX,"Ben",FieldStat.WEIGHT_MIN
);
Map<String,Number> stats = students.stream()
.filter(st -> mapStat.containsKey(st.getName()))
.collect(Collectors.groupingBy(
Student::getName,Collectors.summarizingInt(st -> mapStat.get(st.getName()).getField().applyAsInt(st))
))
.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,e -> mapStat.get(e.getKey()).getStat().apply(e.getValue())
));
System.out.println(stats);
输出
{John=32.0,Ben=65,Mary=167}
,
我认为您正在考虑正确的问题。您当然可以尝试循环以避免多次通过同一数据流。但是,还有更多有趣的流方式可以做到这一点。
List<String> goodNames = ...put in names you want to see...;
Map<String,List<Student>> postsPerName = students.stream().filter(goodNames::contains).collect(groupingBy(Student::getName));
然后你就有了一张地图,其中只有你关心的名字和所有与其名字相匹配的元素。
这是“完美”的解决方案吗?也许,也许不是。但这一切都是平衡的。祝你好运!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。