【问题标题】:Java stream averagingInt by multiple parametersJava流averagingInt由多个参数
【发布时间】:2021-04-03 19:26:18
【问题描述】:

我有课

static class Student {

    private String surname;
    private String firstName;
    private String secondName;
    private int yearOfBirth;
    private int course;
    private int groupNumber;
    private int mathGrade;
    private int engGrade;
    private int physicGrade;
    private int programmingGrade;
    private int chemistryGrade;

还有一种方法可以将学生添加到课程的地图中

public Map<Integer, Double> averageInt(List<Student> students) {
            Map<Integer, Double> map2 = students.stream()
                    .collect(Collectors.groupingBy(Student::getCourse,
                            Collectors.averagingInt(Student::getEngGrade)));
            return map2;
        }

但是,我需要同时在一张地图中使用多个平均值。 不仅是engGrade,还有mathGrade、programmingGrade等等。我认为这种情况下的代码格式应该是Map&lt;Integer, List&lt;Double&gt;&gt; 但我不知道该怎么做。请告诉我

比如我现在显示"Course = 1, average eng grade = ..."

而我需要显示"Course = 1, average eng grade = ..., average math grade = ...",即让map中有多个Double值

【问题讨论】:

    标签: java collections java-stream


    【解决方案1】:

    我建议用这个方法

        public static Map<Integer, Double> averageInt(List<Student> students, ToIntFunction<? super Student> mapper) {
            Map<Integer, Double> map2 = students.stream()
                    .collect(Collectors.groupingBy(Student::getCourse, Collectors.averagingInt(mapper)));
            return map2;
        }
    

    并像这样使用它

    Student.averageInt(students, Student::getMathGrade);
    Student.averageInt(students, Student::getProgrammingGrade);
    

    【讨论】:

    • 谢谢!这个方法对我很好!
    【解决方案2】:

    我看到了两种实现方式


    Collectors#teeing

    如果您使用 或更高版本,则可以使用Collectors::teeing

    返回一个由两个下游收集器组合而成的收集器。

    public static Map<Integer, List<Double>> averageInt(List<Student> students) {
        return students.stream()
                .collect(Collectors.groupingBy(
                        Student::getCourse,
                        Collectors.teeing(
                                Collectors.teeing(
                                        Collectors.averagingInt(Student::getEngGrade),
                                        Collectors.averagingInt(Student::getMathGrade),
                                        (englishAverage, mathAverage) -> {
                                            List<Double> averages = new ArrayList<>();
                                            averages.add(englishAverage);
                                            averages.add(mathAverage);
                                            return averages;
                                        }
                                ),
                                Collectors.averagingInt(Student::getPhysicGrade),
                                (averages, physicsAverage) -> {
                                    averages.add(physicsAverage);
                                    return averages;
                                }
                        )
                ));
    }
    

    它给出了以下结果

    public static void main(String[] args) {
        Student studentOne = new Student(1, 5, 1, 1);
        Student studentTwo = new Student(1, 1, 9, 2);
        Student studentThree = new Student(1, 2, 9, 3);
        Student studentFour = new Student(2, 5, 6, 4);
        Student studentFive = new Student(2, 8, 1, 5);
        Student studentSix = new Student(3, 3, 6, 0);
        Student studentSeven = new Student(3, 5, 7, 7);
        Student studentEight = new Student(3, 3, 6, 8);
        Student studentNine = new Student(3, 4, 1, 9);
        Student studentTen = new Student(4, 9, 1, 0);
    
        List<Student> students = List.of(studentOne, studentTwo, studentThree, studentFour, studentFive, studentSix, studentSeven, studentEight, studentNine, studentTen);
    
        System.out.println(averageInt(students));
    }
    

    结果

    {
        1 = [
            6.333333333333333, 
            2.6666666666666665, 
            2.0
        ], 
        2 = [
            3.5, 
            6.5, 
            4.5
        ], 
        3 = [
            5.0, 
            3.75, 
            6.0
        ], 
        4 = [
            1.0, 
            9.0, 
            0.0
        ]
    }
    

    使用客户收集器

    但是,如果您更喜欢使用客户 Collector,以下是实现此目的的方法。为了方便起见,我在这里选择使用Map 而不是List,但是您当然也可以使用List 而不会改变此方法的本质

    public static Map<Integer, Map<GradeType, Double>> averageInt(List<Student> students) {
        return students.stream()
                .collect(Collectors.groupingBy(
                        Student::getCourse,
                        new CustomCollector(Map.of(
                                GradeType.MATH, Student::getMathGrade,
                                GradeType.ENGLISH, Student::getEngGrade,
                                GradeType.PHYSICS, Student::getPhysicGrade
                        ))
                ));
    }
    
    private enum GradeType {
        MATH, ENGLISH, PHYSICS
    }
    
    private static class CustomCollector implements Collector<Student, Map<GradeType, List<Double>>, Map<GradeType, Double>> {
    
        private final Map<GradeType, Function<Student, Integer>> functionsPerGradeType;
    
        public CustomCollector(Map<GradeType, Function<Student, Integer>> functionsPerGradeType) {
            this.functionsPerGradeType = functionsPerGradeType;
        }
    
        @Override
        public Supplier<Map<GradeType, List<Double>>> supplier() {
            return HashMap::new;
        }
    
        @Override
        public BiConsumer<Map<GradeType, List<Double>>, Student> accumulator() {
            return (map, student) -> {
                for (Map.Entry<GradeType, Function<Student, Integer>> entry : functionsPerGradeType.entrySet()) {
                    GradeType gradeType = entry.getKey();
                    Double gradeForStudent = entry.getValue().apply(student).doubleValue();
                    map.computeIfAbsent(gradeType, gt -> new ArrayList<>());
                    map.get(gradeType).add(gradeForStudent);
                }
            };
        }
    
        @Override
        public BinaryOperator<Map<GradeType, List<Double>>> combiner() {
            return (mapOne, mapTwo) -> {
                mapOne.forEach((k, v) -> {
                    mapTwo.merge(k, v, (listOne, listTwo) -> {
                        listOne.addAll(listTwo);
                        return listOne;
                    });
                });
                return mapTwo;
            };
        }
    
        @Override
        public Function<Map<GradeType, List<Double>>, Map<GradeType, Double>> finisher() {
            return map -> {
                Map<GradeType, Double> finishedMap = new HashMap<>();
    
                for (var entry : map.entrySet()) {
                    GradeType gradeType = entry.getKey();
                    double gradeTypeAverage = entry.getValue().stream().mapToDouble(x -> x).average().orElse(0d);
                    finishedMap.put(gradeType, gradeTypeAverage);
                }
    
                return finishedMap;
            };
        }
    
        @Override
        public Set<Characteristics> characteristics() {
            return Set.of(UNORDERED);
        }
    }
    

    提供以下结果

    {1={PHYSICS=2.0, ENGLISH=6.333333333333333, MATH=2.6666666666666665}, 2={PHYSICS=4.5, ENGLISH=3.5, MATH=6.5}, 3={PHYSICS=6.0, ENGLISH=5.0, MATH=3.75}, 4={PHYSICS=0.0, ENGLISH=1.0, MATH=9.0}}
    

    【讨论】:

      【解决方案3】:

      需要实现一个对象来收集每个学生每门课程的统计信息,它应该有一个接受Student 实例的构造函数来填充相关字段,以及一个用于计算总数/计数的合并函数:

      static class GradeStats {
          int count = 1;
          int course;
          int totalMath;
          int totalEng;
          int totalPhysic;
          
          GradeStats(Student student) {
              this.course = student.getCourse();
              this.totalMath = student.getMath();
              this.totalEng = student.getEng();
              this.totalPhysic = student.getPhysic();
          }
          
          GradeStats merge(GradeStats stats) {
              count++;
              this.totalMath += stats.totalMath;
              this.totalEng += stats.totalEng;
              this.totalPhysic += stats.totalPhysic;
              
              return this;
          }
          
          public String toString() {
              return String.format("course: %d, avg.math: %s; avg.eng: %s; avg.physics: %s",
              course, (double) totalMath / count, (double) totalEng / count, (double) totalPhysic / count);
          }
      }
      

      然后可以使用Collectors.toMap收集统计信息:

      // using simpler Student version with math, eng, and physics grades
      List<Student> students = Arrays.asList(
          new Student(1, 80, 80, 80), new Student(1, 88, 86, 92), new Student(2, 93, 88, 87));
      
      Map<Integer, GradeStats> statMap = students.stream()
              .collect(Collectors.toMap(Student::getCourse, GradeStats::new, GradeStats::merge));
      
      statMap.forEach((k, v) -> System.out.println(k + " -> " + v));
      

      输出

      1 -> course: 1, avg.math: 84.0; avg.eng: 83.0; avg.physics: 86.0
      2 -> course: 2, avg.math: 93.0; avg.eng: 88.0; avg.physics: 87.0
      

      【讨论】:

        【解决方案4】:

        您可以使用 Hashmap 的 HashMap。外部 hashmap 键是课程编号,值是 EnumMap,其中包含您想要的特定课程平均值的枚举。

        public enum Averages {
            MATHAVG, PROGRAMMINGAVG, etc..
        }
        

        从中检索类似于

        courseMap.get(courseNumber).get(Averages.MATHAVG);
        

        【讨论】:

          【解决方案5】:

          代码中描述的步骤:

          import lombok.AllArgsConstructor;
          import lombok.Data;
          import lombok.NoArgsConstructor;
          
          import java.util.HashMap;
          import java.util.List;
          import java.util.function.Supplier;
          
          public class Main {
          
          /**
           * Student holder
           */
          @Data
          @AllArgsConstructor
          static class Student {
              private int course;
              private int mathGrade;
              private int engGrade;
              private int physicGrade;
          }
          
          /**
           * Hold average grades and total count of students
           */
          @Data
          @NoArgsConstructor
          static class AverageGrades {
              private int count = 0;
              private double mathGrade;
              private double engGrade;
              private double physicGrade;
          
              /**
               * Add new math grade
               * @param mathGrade Student grade or Average students grade
               * @param count Number of students
               */
              void addMathGrade(double mathGrade, int count) {
                  this.mathGrade = (this.mathGrade * this.count + mathGrade * count) / (this.count + count);
              }
          
          
              /**
               * Add new eng grade
               * @param engGrade Student grade or Average students grade
               * @param count Number of students
               */
              void addEngGrade(double engGrade, int count) {
                  this.engGrade = (this.engGrade * this.count + engGrade * count) / (this.count + count);
              }
          
          
              /**
               * Add new physic grade
               * @param physicGrade Student grade or Average students grade
               * @param count Number of students
               */
              void addPhysicGrade(double physicGrade, int count) {
                  this.physicGrade = (this.physicGrade * this.count + physicGrade * count) / (this.count + count);
              }
          
              /**
               * Combine average grades
               * @param grades Other grades
               */
              void addGrades(AverageGrades grades) {
                  if (grades.getCount() > 0) {
                      addEngGrade(grades.getEngGrade(), grades.getCount());
                      addMathGrade(grades.getMathGrade(), grades.getCount());
                      addPhysicGrade(grades.getPhysicGrade(), grades.getCount());
                      setCount(getCount() + grades.getCount());
                  }
              }
          }
          
          /**
           * Combine output from multiple threads to left side
           * @param left One thread output
           * @param right Second thread output
           */
          static void gradeCombiner(HashMap<Integer, AverageGrades> left, HashMap<Integer, AverageGrades> right) {
              right.forEach((course, rightGrades) -> {
                  if (left.containsKey(course)) {
                      left.get(course).addGrades(rightGrades);
                  } else {
                      left.put(course, rightGrades);
                  }
              });
          }
          
          /**
           * Student consumer
           * @param map Initialized map
           * @param student Next student
           */
          static void studentConsumer(HashMap<Integer, AverageGrades> map, Student student) {
              if (!map.containsKey(student.getCourse())) {
                  map.put(student.getCourse(), new AverageGrades());
              }
              var grades = map.get(student.getCourse());
              grades.addEngGrade(student.getEngGrade(), 1);
              grades.addMathGrade(student.getMathGrade(), 1);
              grades.addPhysicGrade(student.getPhysicGrade(), 1);
              grades.setCount(grades.getCount() + 1);
          }
          
          static List<Student> mockStudents() {
              return List.of(
                      new Student(1, 2, 3, 4),
                      new Student(1, 3, 5, 2),
                      new Student(2, 3, 5, 2),
                      new Student(3, 3, 5, 2),
                      new Student(3, 3, 5, 2),
                      new Student(3, 3, 5, 2),
                      new Student(3, 3, 5, 8),
                      new Student(3, 3, 11, 2),
                      new Student(3, 9, 5, 2)
              );
          }
          
          
          public static void main(String[] args) {
              var students = mockStudents(); // Mock some students
              Supplier<HashMap<Integer, AverageGrades>> supplier = HashMap::new; // Create new map as supplier
          
              var courseGrades = students.stream().collect(// Standard stream
                      supplier, // Init response
                      Main::studentConsumer, // Apply every student
                      Main::gradeCombiner); // Combine in case of multiple outputs, skipped in this case
              courseGrades.forEach((course, grades) -> {
                  var output = String.format("course: %s average grades: %s", course, grades);
                  System.out.println(output);
              });
              courseGrades = students.parallelStream().collect(// Parallel stream
                      supplier, // Init response
                      Main::studentConsumer, // Apply every student
                      Main::gradeCombiner); // Combine outputs from different threads
              courseGrades.forEach((course, grades) -> {
                  var output = String.format("course: %s average grades: %s", course, grades);
                  System.out.println(output);
              });
          }
          }
          

          输出:

              // course: 1 average grades: Main.AverageGrades(count=2, mathGrade=2.5, engGrade=4.0, physicGrade=3.0)
              // course: 2 average grades: Main.AverageGrades(count=1, mathGrade=3.0, engGrade=5.0, physicGrade=2.0)
              // course: 3 average grades: Main.AverageGrades(count=6, mathGrade=4.0, engGrade=6.0, physicGrade=3.0)
              // course: 1 average grades: Main.AverageGrades(count=2, mathGrade=2.5, engGrade=4.0, physicGrade=3.0)
              // course: 2 average grades: Main.AverageGrades(count=1, mathGrade=3.0, engGrade=5.0, physicGrade=2.0)
              // course: 3 average grades: Main.AverageGrades(count=6, mathGrade=4.0, engGrade=6.0, physicGrade=3.0)
          

          【讨论】:

            猜你喜欢
            • 2017-12-18
            • 1970-01-01
            • 2020-05-31
            • 1970-01-01
            • 2015-02-25
            • 2018-03-20
            • 1970-01-01
            • 2016-07-16
            • 1970-01-01
            相关资源
            最近更新 更多