简体   繁体   中英

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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM