Java 8, Lambda: Sorting within grouped Lists and merging all groups to a list

后端 未结 9 1322
难免孤独
难免孤独 2021-02-09 04:21

Based on the following answer: https://stackoverflow.com/a/30202075/8760211

How to sort each group by stud_id and then return a List with all Students as result of the g

相关标签:
9条回答
  • 2021-02-09 04:38

    If I get you right, you want a List<Student> (not a map) where students are grouped by their locations and sorted by ids inside groups and where groups are also sorted by ids, not by location names. This is possible, but requires one grouping and two sortings:

    //first, use your function to group students
    Map<String, List<Student>> studlistGrouped = students.stream()
            .collect(Collectors.groupingBy(Student::getLocation, Collectors.toList()));
    
    //then sort groups by minimum id in each of them
    List<Student> sorted = studlistGrouped.entrySet().stream()
            .sorted(Comparator.comparing(e -> e.getValue().stream().map(Student::getId).min(Comparator.naturalOrder()).orElse(0)))
            //and also sort each group before collecting them in one list
            .flatMap(e -> e.getValue().stream().sorted(Comparator.comparing(Student::getId))).collect(Collectors.toList());
    

    This will produce following:

    Student{id='1726', name='John', location='New York'}
    Student{id='3442', name='Mark', location='New York'}
    Student{id='5223', name='Michael', location='New York'}
    Student{id='2234', name='Andrew', location='Los Angeles'}
    Student{id='4321', name='Max', location='California'}
    Student{id='7765', name='Sam', location='California'}
    

    Maybe this can be done more elegantly, suggestions are welcome

    EDIT: At the time this answer was written there was no mention about Grouping based on the order of the Elements in the origin List in the OPs question. So my assumption was to sort both list and groups by ids. For solutions based on the order in the original list see other answers, for example, the Holgers one

    0 讨论(0)
  • 2021-02-09 04:38

    Since the result is supposed to be a list, you‘re not grouping but simply sorting (in the sense of changing the order according to a defined rule). The main obstacle is that you want the locations to be ordered after their first encounter in the original list.

    The straight-forward approach is to fix this location order first, followed by a single sort operation:

    Map<String,Integer> locationOrder = studlist.stream()
        .collect(HashMap::new,
                 (m,s)->m.putIfAbsent(s.stud_location, m.size()),
                 (m1,m2)->m2.keySet().forEach(l->m1.putIfAbsent(l, m1.size())));
    
    studlist.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
                            .thenComparing(s -> s.stud_id));
    

    If you can not or do not want to modify the original list, you can simply use a copy:

    List<Student> result = new ArrayList<>(studlist);
    result.sort(Comparator.comparingInt((Student s) -> locationOrder.get(s.stud_location))
                          .thenComparing(s -> s.stud_id));
    

    It’s also possible to solve this with a grouping operation, but that’s not easier:

    List<Student> result = studlist.stream()
        .collect(Collectors.collectingAndThen(
                    Collectors.groupingBy(s -> s.stud_location,
                                          LinkedHashMap::new, Collectors.toList()),
                    m -> m.values().stream()
                          .flatMap(l -> l.stream().sorted(Comparator.comparing(s->s.stud_id)))
                          .collect(Collectors.toList())));
    

    Note that you have to collect into a LinkedHashMap to ensure that the order of the groups is retained.

    0 讨论(0)
  • 2021-02-09 04:38

    You can add one line:

    studlistGrouped.values().forEach(list -> list.sort(Comparator.comparing(Student::getId)));
    

    Or you can write your own collector.

    I know which one I would choose.

    0 讨论(0)
  • 2021-02-09 04:39

    If you just want to group and sort, you don't need a groupingBy() collector, or even a stream at all. Just use a composite sort:

    studlist.sort(Comparator.comparing(Student::getLocation).thenComparing(Student::getId));
    
    0 讨论(0)
  • 2021-02-09 04:40

    not 100% clear whether you're expected a Map<String, List<Student>> or just a List<Student>, nevertheless here are both solutions:

    imports:

    import static java.util.stream.Collectors.*;
    import java.util.*;
    import java.util.function.Function;
    

    retrieving a Map<String, List<Student>> where each List<Student> contains students sorted by their ids.

    Map<String, List<Student>> resultSet = studlist.stream()
          .collect(groupingBy(Student::getLocation,
                 mapping(Function.identity(),
                      collectingAndThen(toList(),
                          e -> e.stream().sorted(Comparator.comparingInt(Student::getId))
                                                .collect(toList())))));
    

    on the other hand, if you want to retrieve just a list of Student objects sorted by a given property then it would be a waste of resources to perform a groupingBy, sorted, collect and somehow reduce the map values into a single list. Rather just sort the Student objects within the list providing a sort key i.e.

    studlist.sort(Comparator.comparingInt(Student::getId));
    

    or

    studlist.sort(Comparator.comparing(Student::getLocation));
    

    or depending on whether you want to sort by more than one property then you could do something like shmosel's answer.

    0 讨论(0)
  • 2021-02-09 04:41

    You don't need to group by location, as the input and output have same datatype. You can just chain multiple Comparators and sort the input.

    List<Student> groupedAndSorted = studlist.stream()
           .sorted(Comparator.comparing(Student::getStudLocation)
                    .thenComparing(Comparator.comparing(Student::getStudId)))
                    .thenComparing(Comparator.comparing(Student::getStudName)))
           .collect(Collectors.toList());
    

    As an offtopic, I would seggest that you change you data structure to use Lombok to auto-generate getters/setters/constructors https://projectlombok.org/features/Data . You should also use a more generic naming conversion, i.e. remove the "stud_" prefix of attributes.

    import lombok.Data;
    @Data
    class Student {
        String id;
        String name;
        String location;
    }
    
    0 讨论(0)
提交回复
热议问题