Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java stream - groupingBy by a nested list (list in a second order)

I have the following data structure -

List of Students that each holds a lists of States that each holds a list of cities.

public class Student {
    private int id;
    private String name;
    private List<State> states = new ArrayList<>();
}

public class State {
    private int id;
    private String name;
    private List<City> Cities = new ArrayList<>();
}

public class City {
    private int id;
    private String name;
}

I want to get the following.

Map<String, Students> citiesIdsToStudensList;

I write the following

Map<Integer, List<Integer>> statesToStudentsMap = students.stream()
            .flatMap(student -> student.getStates().stream())
            .flatMap(state -> state.getCities().stream())
            .collect(Collectors.groupingBy(City::getId, Collectors.mapping(x -> x.getId(), Collectors.toList())));

But it doesn't get me the result I want.

like image 741
Bick Avatar asked Apr 30 '16 21:04

Bick


1 Answers

Using the Stream API, you'll need to flat map twice and map each intermediate student and city into a tuple that is capable of holding the student.

Map<Integer, List<Student>> citiesIdsToStudentsList =
    students.stream()
            .flatMap(student -> student.getStates().stream().map(state -> new AbstractMap.SimpleEntry<>(student, state)))
            .flatMap(entry -> entry.getValue().getCities().stream().map(city -> new AbstractMap.SimpleEntry<>(entry.getKey(), city)))
            .collect(Collectors.groupingBy(
                entry -> entry.getValue().getId(),
                Collectors.mapping(Map.Entry::getKey, Collectors.toList())
            ));

However, it would maybe be cleaner to use nested for loops here:

Map<Integer, List<Student>> citiesIdsToStudentsList = new HashMap<>();
for (Student student : students) {
    for (State state : student.getStates()) {
        for (City city : state.getCities()) {
            citiesIdsToStudentsList.computeIfAbsent(city.getId(), k -> new ArrayList<>()).add(student);
        }
    }
}

This leverages computeIfAbsent to populate the map and creates a list of each student with the same city id.

like image 184
Tunaki Avatar answered Sep 22 '22 03:09

Tunaki