Reputation: 18521
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.
Upvotes: 8
Views: 4550
Reputation: 298153
In addition to Tunaki’s answer, you can simplify it as
Map<Integer, List<Student>> citiesIdsToStudentsList =
students.stream()
.flatMap(student -> student.getStates().stream()
.flatMap(state -> state.getCities().stream())
.map(state -> new AbstractMap.SimpleEntry<>(student, state.getId())))
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
));
It utilizes the fact that you are not actually interested in State
objects, so you can flatMap
them directly to the desired City
objects, if you do it right within the first flatMap
operation. Then, by performing the State.getId
operation immediately when creating the Map.Entry
, you can simplify the actual collect
operation.
Upvotes: 0
Reputation: 137084
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.
Upvotes: 6