Reputation: 31
I have class student as below
class Student{
Map<String,Integer> subjectMarks;
String name;
public Student(Map<String,Integer> subject, String name) {
super();
this.subjectMarks = subject;
this.name = name;
}
public Map<String,Integer> getSubjectMarks() {
return subjectMarks;
}
public void setSubjectMarks(Map<String,Integer> subject) {
this.subjectMarks = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
In main method we add student object in arraylist as follow.
ArrayList<Student> arr = new ArrayList<Student>();
Map m1 = new HashedMap();
m1.put("Maths",40);
m1.put("Science",50);
Map m2 = new HashedMap();
m2.put("Maths",60);
m2.put("Science",20);
arr.add(new Student(m1, "RAJ"));
arr.add(new Student(m2, "AMIT"));
can some help/guide me to find average marks of subject for each student and then get maximum from averge. I want help to write this snippet in java8
Upvotes: 2
Views: 4045
Reputation: 602
Do not limit yourself to the idea of streams in Java 8 where you have to process a stream result directly into the next stream and so on... The efficiency might not be the best but consider to nest loops.
Start to think what you have: several marks for every Student
. You want to find the average of these marks for every Student
. You can reduce the problem to first think on how to get the average for one Student
.
double average = student.getSubjectMarks().values().stream()
.mapToInt(Integer::valueOf).average().orElse(0.0);
Even though your examples show only integer numbers, averages can also be floating point numbers.
Then, you would have to loop through all students and execute the procedure above for every student.
Map<String, Double> studentAverages = new HashMap<>();
arr.forEach(student -> {
double average = student.getSubjectMarks().values().stream()
.mapToInt(Integer::valueOf).average().orElse(0.0);
studentAverages.put(student.getName(), average);
});
In the described implementation, the required averages are saved in the Map
studentAverages
which has the names of the students as a key and the average mark as a value.
You can then simply get the maximum integer from your list.
studentAverages.values().stream().mapToDouble(Double::doubleValue).max();
Some answers provide more sophisticated usages of streams. However, the code above is more readable. Furthermore, the data type Object
is very general, difficult for further usage and error prone.
Upvotes: 1
Reputation: 40062
There is nothing wrong with using streams to solve this as you requested.
Object[] result = arr.stream().map(s -> new Object[] {
s.getName(),
s.getSubjectMarks().values().stream()
.mapToInt(Integer::valueOf)
.summaryStatistics().getAverage() })
.max(Comparator.comparing(obj -> (double) obj[1]))
.get();
System.out.println(Arrays.toString(result));
Prints
[RAJ, 45.0]
Upvotes: 0
Reputation: 945
As @Felix noted in his answer, since you have a nested collection it is difficult to process in a single stream. You can use one stream to calculate the average for each student and another to calculate the max of the averages.
Start with a separate function to calculate the average for a student:
private OptionalDouble calculateAverageMarks(Student student) {
return student.getSubjectMarks().values().stream()
.mapToInt(Integer::intValue)
.average();
}
Note that you could add .orElse(0.0)
(or some other value) to the pipeline if you wanted to return a double
, but that wouldn't allow you to later distinguish between a student with all 0's and one not enrolled in any subjects.
Then you can collect the averages into a map.
Map<String, OptionalDouble> averageMarks = arr.stream()
.collect(Collectors.toMap(Student::getName, this::calculateAverageMarks));
Note that using Student::getName
in the collector will throw if two students share the same name. You could use Function.identity()
instead to ensure that each key will be distinct as long as you don't override equals
in Student
.
If you want, you can then remove students with no subjects
averageMarks.values().removeIf(v -> !v.isPresent());
In Java 11, you can use OptionalDouble::isEmpty
instead of the lambda.
Then you can map the values to a double stream and get the max
OptionalDouble max = averageMarks.values().stream()
.filter(OptionalDouble::isPresent) // In case you didn't remove the empty marks
.mapToDouble(OptionalDouble::getAsDouble)
.max();
Upvotes: 0