Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java stream averagingInt by multiple parameters

I have a class

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;

And there is a method that adds students to the map for the course

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

However, I need several average values ​​in one map at the same time. Not only engGrade, but also mathGrade, programmingGrade and so on. I think the code in this case should be in the format Map<Integer, List<Double>> but I don’t know how to do it. Tell me please

For example, I now display "Course = 1, average eng grade = ..."

And I need to display "Course = 1, average eng grade = ..., average math grade = ...", ie so that there are multiple Double values ​​in the map

like image 666
Nikita Avatar asked Apr 03 '21 19:04

Nikita


People also ask

How to filter a stream of objects in Java 8?

Java 8 stream – multiple filters example. Learn to filter a stream of objects using multiple filters and process filtered objects by either collecting to a new list or calling method on each filtered object. 1. Create simple predicates using lambda expression. In Java streams API, a filter is a Predicate instance (boolean-valued function).

How to add multiple conditions to stream objects in Java?

You can use predicate or () or isEquals () methods with the multiple predicate conditions. 4. Conclusion You learned how to use the filter () method and the predicate and () method to add multiple conditions to stream objects in this article.

Why do we need to average values in Java streams?

Java designers understand that it is not necessary that the objects in the stream themselves may be numeric; rather these objects may have a numerical attribute which needs to be averaged, or their might be some calculation done first on the objects to derive an equivalent numerical value and then average those values.

How to filter Java streams with single condition?

GitHub link is given at the end of the article for the shown examples. 2. Stream.filter () with Single Condition First, We'll start by looking at how to apply the single filter condition to java streams. Predicate is passed as an argument to the filter () method. Each value in the stream is evaluated to this predicate logic.


2 Answers

I propose to use this method

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

And use it like this

Student.averageInt(students, Student::getMathGrade);
Student.averageInt(students, Student::getProgrammingGrade);
like image 93
Ahmed HENTETI Avatar answered Sep 20 '22 23:09

Ahmed HENTETI


I see two ways of achieving this

  • Using java-12's Collectors::teeing
  • Using a customer collector (scroll down)

Collectors#teeing

If you're using java-12 or higher, you could use Collectors::teeing

Returns a Collector that is a composite of two downstream collectors.

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

And it gives the following results

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

Result

{
    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
    ]
}

Using a customer collector

However, if you prefer using a customer Collector, here is how to achieve this. I choose to use a Map instead of a List here for conveniency, but you can of course use a List too without changing the essence of this method

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

Providing the following result

{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}}
like image 35
Yassin Hajaj Avatar answered Sep 20 '22 23:09

Yassin Hajaj