简体   繁体   English

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

[英]Should I use several streams o a single loop?

Imagine we are working with this class:想象一下我们正在使用这个 class:

public Student{
  String name;
  Integer age;
  Integer height;
  Integer weight;
}

Now we've got a list of students and we are asked something like:现在我们有一个学生名单,我们被问到这样的问题:

  • filter those whose name is "Jonh" and get the average age过滤名字为“Jonh”的人,得到平均年龄
  • filter those whose name is "Mary" and get the biggest hight过滤那些名字叫“玛丽”的人,得到最大的高度
  • filter those whose name is "Ben" and get the smallest weight过滤名字为“Ben”的,得到最小的权重

I think thar a clean and understandble solution is to use lambdas to filter by name and get what is asked for:我认为一个干净且易于理解的解决方案是使用 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()

I guess that at least it is iterating over the list 3 times, and using a single loop with conditions might be more efficent:我想至少它在列表上迭代了 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;

I think that the first way is cleaner but the second one is faster.我认为第一种方式更干净,但第二种方式更快。 Am I right?我对吗? Which one shoud I use?我应该使用哪一个? Imagine that the list of students contains a huge number of elements, and there are more names than Jonh, Mary and Ben.想象一下,学生名单包含大量元素,名字的数量比 Jonh、Mary 和 Ben 还多。

This can be implemented with Stream API in the following way:这可以通过以下方式使用 Stream API 实现:

  1. Create a map of names to the appropriate getters of Student classStudent class 的相应吸气剂创建名称的 map
  2. Create another map of names to the getters for IntSummaryStatistics class为 IntSummaryStatistics class 的吸气剂创建另一个名称的IntSummaryStatistics
  3. Build a map with Collectors.groupingBy + Collectors.summarizingInt to get the stats per student, and transform this map with Collectors.toMap to get required stats parameter by the student.使用Collectors.groupingBy + Collectors.summarizingInt构建 map 以获取每个学生的统计信息,并使用Collectors.toMap转换此 map 以获得学生所需的统计参数。

Update更新
An enum may be implemented to store references to appropriate getters in Student and IntSummaryStatistics , then the map should use the enum values.可以实现一个枚举来存储对StudentIntSummaryStatistics中适当 getter 的引用,然后 map 应该使用枚举值。

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;
    }
}

Example implementation:示例实现:

List<Student> students = Arrays.asList(
    new Student("John", 35, 177, 78), new Student("John", 29, 173, 72),
    new Student("Mary", 32, 164, 58), new Student("Mary", 28, 167, 56),
    new Student("Ben",  24, 181, 84), new Student("Ben",  27, 169, 65),
    new Student("Bob",  35, 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);

Output Output

{John=32.0, Ben=65, Mary=167}

I think you're thinking of the right problems.我认为你正在考虑正确的问题。 You could certainly try looping to avoid multiple passes over the same stream of data.您当然可以尝试循环以避免多次传递相同的 stream 数据。 However, there are also more fun stream-ways to do this.但是,还有更多有趣的流方式可以做到这一点。

List<String> goodNames = ...put in names you want to see...;
Map<String, List<Student>> postsPerName = students.stream().filter(goodNames::contains).collect(groupingBy(Student::getName));

Then you have a map with only names you care about and all elements matching their names.然后你有一个 map,只有你关心的名字和所有匹配他们名字的元素。

Is this the "perfect" solution?这是“完美”的解决方案吗? Maybe, maybe not.也许,也许不是。 But it's all a balance.但这都是一种平衡。 Good luck!祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM