flamingo
flamingo

Reputation: 21

How to iterate only once java stream

I have a data Map<String, List<Students>> where the key is the subject name.

I want to iterate through each subject and add the score of other subjects corresponding to a student.

I tried the follo

wing:

    data.entrySet().stream().forEach(subject -> sumScoresOfAStudent(subject, data));
    
    private void sumScoresOfAStudent(Entry<String, List<Student>> subject, Map<String, List<Student>> data) {
            String firstSubject = subject.getKey();
            List<Student> firstSubjectScores = subject.getValue();
            List<Student> otherSubjectScores = data.entrySet().stream()
                    .filter(subjectData -> !subjectData.getKey().equalsIgnoreCase(firstSubject))
                    .map(subjectData -> subjectData.getValue()).flatMap(subjectData -> subjectData.stream())
                    .collect(Collectors.toList());
            ...
        }

But here I noticed it would loop 3 times for the same data if I have 3 subjects as my Key. Is there any other better way to iterate it only once ?

Thanks in advance for your help.

Eg:
        Student s11 = new Student("A", 20);
        Student s21 = new Student("B", 10);
        data.put("Maths", students);
        Student s12 = new Student("A", 30);
        Student s22 = new Student("B", 40);
        data.put("Science", students);
        Student s13 = new Student("A", 45);
        Student s23 = new Student("B", 20);
        data.put("History", students);

Expected Output :
Student A - 95
Student B - 70

Upvotes: 2

Views: 1128

Answers (3)

Bohemian
Bohemian

Reputation: 425033

I think your life would be easier if you first created a map of student to their subjects:

Map<Student, List<String>> studentSubjects = new HashMap<>();
data.forEach((k, v) -> v.forEach(studentSubjects.computeIfAbsent(v, x -> new ArrayList<>()).add(k)));

But your “Student” class does not actually represent a student, but rather a student’s result for a subject. You should fix that too.

Upvotes: 0

fps
fps

Reputation: 34460

You have a map of lists aka a multimap and it seems that you want to get the total scores per student. You could use a Map<String, Integer> for this, with the key being the student name and the value the total score.

Assuming Student has getName() and getScore() methods, here's a way to do what you want without streams:

Map<String, Integer> result = new LinkedHashMap<>();
data.forEach((k, students) -> 
    students.forEach(s -> result.merge(s.getName(), s.getScore(), Integer::sum)));

This uses Map.forEach, Iterable.forEach and Map.merge. Check the docs to see how they work.

First, we are iterating the map. Then, for each entry (k, students) of the map, we iterate the values (which is a list of students). For each element student of the list, we put their name and score in the result map using the Map.merge method.

The Map.merge method works the same as Map.put, but if there's already an entry with the same key, it merges the present value with the new value using the 3rd argument (in this case Integer::sum) which, well, sums the old and new values for that key.

Upvotes: 0

WJS
WJS

Reputation: 40034

Sorry, I entirely misunderstood what you wanted. This presumes the Student class has the getters getScore and getName. This simply takes the map values which are lists of students and the flattens them into one large stream and computes the sum of each students score.


Map<String, Integer> scores =  data.values().stream().flatMap(List::stream)
        .collect(Collectors.groupingBy(Student::getName,
                Collectors.summingInt(Student::getScore)));

scores.forEach((name,score) -> System.out.printf("Student %s - %s%n", 
              name,score));

Prints

Student A - 95
Student B - 70

Upvotes: 2

Related Questions