Home > Enterprise >  Create map from list based on list of child objects
Create map from list based on list of child objects

Time:11-09

I have a class which contains list of child objects, like below :

class Student {
   private String name;
   private List<String> subjects;
}

I have a list of Student classes like below :

Student [name = "John", subjects = ["Physics", "Math", "Chemistry"]]
Student [name = "Max",  subjects = ["Physics", "Chemistry"]]
Student [name = "Chris",  subjects = ["Chemistry"]]

I want to group the data such that it will be like below :

Physics = [Student [name = "John", subjects = ["Physics", "Math", "Chemistry"]], 
          Student [name = "Max",  subjects = ["Physics", "Chemistry"]]],
Chemistry = [Student [name = "John", subjects = ["Physics", "Math", "Chemistry"]], 
          Student [name = "Max",  subjects = ["Physics", "Chemistry"]], 
          Student [name = "Chris",  subjects = ["Chemistry"]]] 
Math = [Student [name = "John", subjects = ["Physics", "Math", "Chemistry"]]]

I can group the data using traditional foreach loop, but I want to use stream API for this, but not able to do so. Can you please help?

CodePudding user response:

The first step to this kind of problem is to realize how its elements can be grouped.

The tricky part is that we are not going to group the Students by a simple property, but using an aggregate property, a bundle of Subjects.

One solution is to use each Student's Subject as a key and the Student itself as the value of a Map:

BiConsumer<HashMap<String, List<Student>>, Student> mapStudentBySubject =
                ((hashMap, student) -> student.getSubjects().forEach(subject ->
                        hashMap.computeIfAbsent(subject, k -> new ArrayList<>()).add(student))
                );

The reason for the computIfAbsent method is just to guarantee that there will be aways a list to be used for each Subject

The rest of the code is pretty straightforward:

var studentsGrouped = studentBundle.stream()
                .collect(HashMap::new, mapStudentBySubject, HashMap::putAll);

The full code:

        var john = new Student("John", List.of("Physics", "Math", "Chemistry"));
        var max = new Student("Max", List.of("Physics", "Chemistry"));
        var chris = new Student("Chris", List.of("Chemistry"));

        var studentBundle = List.of(john, max, chris);

        BiConsumer<HashMap<String, List<Student>>, Student> mapStudentBySubject =
                ((hashMap, student) -> student.getSubjects().forEach(subject ->
                        hashMap.computeIfAbsent(subject, k -> new ArrayList<>()).add(student))
                );

        var studentsGrouped = studentBundle.stream()
                .collect(HashMap::new, mapStudentBySubject, HashMap::putAll);

CodePudding user response:

One way to do this, is to map the students into a multiple pair (subject, student) and then grouping them by subject.

List<Student> students = Arrays.asList(
        new Student("John", Arrays.asList("Physics", "Math", "Chemistry")),
        new Student("Max", Arrays.asList("Physics", "Chemistry")),
        new Student("Chris", Arrays.asList("Chemistry"))
);

Map<String, List<Student>> result = students.stream()
        .flatMap(student -> student
                .getSubjects()
                .stream()
                .map(s -> new Object[]{s, student})
        )
        .collect(
                Collectors.groupingBy(pair -> (String) pair[0], 
                Collectors.mapping(pair -> (Student) pair[1], Collectors.toList()))
        );

System.out.println(result);
  • Related