sidd
sidd

Reputation: 305

Average marks of students using Java streams

Given a student class, I need to find all students whose name starts with 'A', in order of their average marks, using Java 8 stream API.

I have tried the below code. The issue is I can easily find out students whose name starts with 'A' by filtering out, but how do I find out the average marks (as my marks are stored in a list - which has subject and score). Also how do I order them? These 2 issues I am completely clueless.

Below is the code

public class Marks {

    private String subject; 
    private int score;

// constructors ....
// getter and setters..
}

…and:

public class Student {
    
    private String name; 
    private int id; 
    private List<Marks> marks;

// constructors..
// getters and setters. 

}

…and:

public class StudentMarks {

    public static void main(String[] args) {
        String name = null; 
        int id = 0; 
        
        int numberOfSubjects = 0;
        String subject = null; 
        int score = 0;
        
        List<Student> students = new ArrayList<Student>();
        List<Marks> marks = new ArrayList<Marks>();         
        
        for(int i = 0; i < numberOfStudents; i++) {
            Student student = new Student();
            
            // code here to enter the name, id and number of subjects
            
            for(int j = 0; j < numberOfSubjects; j++) {
                Marks m = new Marks();
                
                // code to take the subject and score of the student..
                
                m.setSubject(subject);
                m.setScore(score);      
                
                marks.add(m); 
            }
            
            student.setName(name);
            student.setId(id);  
            student.setMarks(marks);
            
            students.add(student);
        }
        
        // This would filter out the students whose name starts with 'A'
        List<Student> names = students.stream().filter(n -> n.getName().startsWith("A")).collect(Collectors.toList());
        
        // Now second step (basically all this is to be done in one step) is to get the average marks and that too in some order (either ascending or descending. How do I do this ???
       OptionalDouble averageMarks = students.stream().filter(n -> n.getName().startsWith("A")).mapToDouble(s -> s.getMarks().get(0).getScore()).average(); 
       
        // I can't have get(0) above   
       //  How do I use mapToDouble to get the average as marks are stored in a list which has subject and score. I need to get the sum and then average for all students and then order them 
       // How do I use mapToDouble to sum the marks for each student and then get an average and then order them (ascending / descending).
    }
}

Upvotes: 1

Views: 7856

Answers (2)

WJS
WJS

Reputation: 40072

I created the classes with the getters and setters. The extra students are to show the filtering is working. Here is how I would do it.

List<Student> list = new ArrayList<>();
List<Marks> m1 = List.of(new Marks("English", 100),
        new Marks("Algebra", 94));
List<Marks> m2 = List.of(new Marks("English", 88),
        new Marks("Calculus", 97));

List<Student> students = List.of(new Student("Amy", 1, m1),
        new Student("Allen", 2, m2),
        new Student("Bob", 3, m1),
        new Student("John", 4, m2));

This is the stream process.

  • first filter on the letter A
  • then, build a TreeMap, with the key being the student's average and the value being the name.
  • the average is from the summarystatics method.
Map<Double, String> averages = students.stream()
        .filter(s -> s.getName().startsWith("A"))
        .collect(Collectors.toMap(
                s -> s.getMarks().stream()
                        .mapToInt(Marks::getScore)
                        .summaryStatistics().getAverage(), s->s.getName(),
                        (a,b)->a, // merge, not used by syntactically required.
                ()->new TreeMap<>(Comparator.reverseOrder())
                ));

averages.forEach((k,v)->System.out.printf("Student: %8s,   Average: %s%n", v,k));

Prints

Student:      Amy,   Average: 97.0
Student:    Allen,   Average: 92.5

If you want the averages in ascending order, just remove the Comparator from the TreeMap.

Upvotes: 3

Kalai
Kalai

Reputation: 543

One quick dirty way could be to sort the filtered stream with the help of a Comparator. However, this way computes the average unnecessarily.

List<Student> nameScoreSortedStudents = students
                .stream()
                .filter(student -> student.getName().startsWith("A"))
                .sorted(averageMarkComparator)
                .collect(Collectors.toList());
    
Comparator<Student> averageMarkComparator = (firstStudent, secondStudent) -> {
            return Double.compare(getAverage(firstStudent), getAverage(secondStudent);};
    
private static double getAverage(Student student){
            return student.getMarks().stream().mapToInt(Marks::getScore).average().orElse(0);}

You could filter based on the name, and then create a map such that the student is the key and the average of the Marks's score is the value and then sort the map based on the value and finally obtain the sorted list of Student.

    List<Student> nameScoreSortedStudents = students
            .stream()
            .filter(student -> student.getName().startsWith("A"))
            .collect(Collectors.toMap(student->student,
                    student->student.getMarks()
                    .stream()
                    .mapToInt(Marks::getScore)
                    .average()
                    .orElse(0)))
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

This way has it's drawbacks too.

Alternatively, you could compute the average and set it as an instance variable( perhaps when you set the `List via setter method or constructor). Then, use it to sort the stream via method reference to the getter of the average instance variable and finally collect it as a sorted list.

Note: There are way more efficient solutions than the ones I have provided. This is to just give you an idea. To change the order of the sorting, just tweak the Comparator given to sorted()

Upvotes: 1

Related Questions