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
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).
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.
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.
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.
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);
I see two ways of achieving this
Collectors::teeing
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
]
}
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}}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With