微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我应该在单个循环中使用多个流吗?

如何解决我应该在单个循环中使用多个流吗?

想象一下我们正在使用这个类:

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 实现:

  1. 创建名称到 Student 类的适当 getter 的映射
  2. IntSummaryStatistics 类创建另一个名称到 getter 的映射
  3. 使用 Collectors.groupingBy + Collectors.summarizingInt 构建地图以获取每个学生的统计数据,并使用 Collectors.toMap 转换此地图以获取学生所需的统计参数。

更新
可以实现一个枚举来存储对 StudentIntSummaryStatistics 中适当的 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 举报,一经查实,本站将立刻删除。