简体   繁体   English

Java 8,Lambda:在分组列表中排序并将所有组合并到列表中

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

Based on the following answer: https://stackoverflow.com/a/30202075/8760211 基于以下答案: 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 grouping by stud_location and then sorting by stud_id)? 如何通过stud_id对每个组进行排序,然后通过stud_location返回一个包含所有学生的List作为分组的结果,然后按stud_id排序?

It would be great to have this as extension to the existing Lambda Expression: 将它作为现有Lambda表达式的扩展会很棒:

Map<String, List<Student>> studlistGrouped =
    studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));

I need the Grouping based on the order of the Elements in the origin List. 我需要基于原始列表中元素的顺序进行分组。

First group: "New York"
Second group: "California"
Third group: "Los Angeles"

1726, "John", "New York"
4321, "Max", "California"
2234, "Andrew", "Los Angeles"
5223, "Michael", "New York"
7765, "Sam", "California"
3442, "Mark", "New York"

The result would then look like the following: 结果将如下所示:

List<Student> groupedAndSorted = ....

    1726, "John", "New York"
    3442, "Mark", "New York"
    5223, "Michael", "New York"
    4321, "Max", "California"
    7765, "Sam", "California"
    2234, "Andrew", "Los Angeles"

I have tried the following: 我尝试过以下方法:

studlistGrouped.entrySet().stream().sorted(Comparator.compar‌​ing(Map.Entry::getVa‌​lue))

But this doesn't work. 但这不起作用。

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. 如果我找到了你,你需要一个List<Student> (不是地图),其中学生按其位置分组, 并按组内的ID排序,组中的组也按ID排序,而不是按位置名称排序。 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. 编辑:在写这个答案的时候,没有提到基于 OPs问题中原始列表元素顺序的分组 So my assumption was to sort both list and groups by ids. 所以我的假设是按ID分类列表和组。 For solutions based on the order in the original list see other answers, for example, the Holgers one 对于基于原始列表中的顺序的解决方案,请参阅其他答案,例如,Holgers one

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. 请注意,您必须收集到LinkedHashMap以确保保留组的顺序。

not 100% clear whether you're expected a Map<String, List<Student>> or just a List<Student> , nevertheless here are both solutions: 不是100%清楚你是否期望一个Map<String, List<Student>>或者只是一个List<Student> ,不过这里都是解决方案:

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>> ,其中每个List<Student>包含按其ID排序的学生。

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. 另一方面,如果您只想检索按给定属性排序的Student对象列表,那么执行groupingBysortedcollect并以某种方式将映射值减少到单个列表将浪费资源。 Rather just sort the Student objects within the list providing a sort key ie 而只是在列表中对Student对象进行排序,提供排序键即ie

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 . 或者根据您是否想要按多个属性排序,那么您可以执行类似shmosel的回答

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. 我知道我会选择哪一个。

try sort first and then groupinBy it works well. 首先尝试排序,然后分组,它运行良好。 The below code sorts the Students within the location. 以下代码对学生在该位置进行排序。

students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation))
The output in this case is 这种情况下的输出是

{New York=[1726  John  New York, 3442  Mark  New York, 5223  Michael  New York], Los Angeles=[2234  Andrew  Los Angeles], California=[4321  Max  California, 7765  Sam  California]}

If you would like to have locations also to be sorted, use a code snippet like below 如果您希望对位置进行排序,请使用下面的代码段

students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation, TreeMap::new, Collectors.toList()))

The output in this case is {California=[4321 Max California, 7765 Sam California], Los Angeles=[2234 Andrew Los Angeles], New York=[1726 John New York, 3442 Mark New York, 5223 Michael New York]} 在这种情况下的输出是{California=[4321 Max California, 7765 Sam California], Los Angeles=[2234 Andrew Los Angeles], New York=[1726 John New York, 3442 Mark New York, 5223 Michael New York]}

Student class implements Comparable and the compareTo method is is based on the id. Student类实现Comparable,compareTo方法基于id。

First, about sorting within each group. 首先,关于每个组内的排序。 Collectors.groupingBy has a second variant which allows you to specify a collector which is used to generate the groups. Collectors.groupingBy有第二个变体,它允许您指定用于生成组的收集器。 The collector you specify can be one which collects the items in a sorted way (eg a TreeSet ), and as a finishing operation transforms it to a sorted list. 您指定的收集器可以是以排序方式收集项目的收集器(例如TreeSet ),并且作为完成操作将其转换为排序列表。 Such a collector can be created with Collectors.collectingAndThen() . 可以使用Collectors.collectingAndThen()创建这样的收集Collectors.collectingAndThen()

For example, with Integers I tried: 例如,我尝试使用Integers:

List<Integer> list = Arrays.asList(9, 2, 43, 6, 5, 3, 87, 56, 87, 67, 77, 22, 23, 1);
System.out.println(
    list.stream().collect(Collectors.groupingBy(
        i -> i % 3,                                         // classifier
        Collectors.collectingAndThen(
            Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector 
            set -> new ArrayList<>(set)))));                // finishing operation

Output: 输出:

{0=[3, 6, 9, 87], 1=[1, 22, 43, 67], 2=[2, 5, 23, 56, 77]}

I'm sure you manage to translate this to your case. 我确定你设法将此翻译成你的情况。 You might need to create a TreeSet with a custom comparator so that the students in each group are ordered the way you want, or, if students are sorted in the same way always, make Student implement Comparable . 您可能需要使用自定义比较器创建一个TreeSet ,以便按照您希望的方式对每个组中的学生进行排序,或者,如果学生总是以相同的方式排序,则使Student实现Comparable

Second about sorting the groups. 其次是关于对组进行排序。 Collectors.groupingBy by default creates a HashMap , which does not have a specified order of the keys (above, the keys are ordered correctly by chance). Collectors.groupingBy默认创建一个HashMap ,它没有指定的键顺序(上面,键是按顺序正确排序的)。 So, to also order the keys, you need to use the Collectors.groupingBy variant which also lets you create the result map, which luckily also exists: 因此,为了对键进行排序,您需要使用Collectors.groupingBy变体,它还允许您创建结果映射,幸运的是,它也存在:

System.out.println(
    list.stream().collect(Collectors.groupingBy(
        i -> i % 3,                                         // classifier
        new TreeMap<>((a, b) -> b.compareTo(a)),            // map creator
        Collectors.collectingAndThen(
            Collectors.toCollection(() -> new TreeSet<>()), // intermediate collector
            set -> new ArrayList<>(set)))));                // finishing operation

I specified a custom comparator for the map to show that the ordering is indeed different. 我为地图指定了一个自定义比较器,以显示排序确实不同。 The result is: 结果是:

{2=[2, 5, 23, 56, 77], 1=[1, 22, 43, 67], 0=[3, 6, 9, 87]}

Now, whether this is more readable and maintainable than a good-old pre-Java 8 solution is a matter of taste... 现在,这是否比一个古老的Java 8之前的解决方案更具可读性和可维护性是一个品味问题......

If you just want to group and sort, you don't need a groupingBy() collector, or even a stream at all. 如果您只想分组和排序,则不需要groupingBy()收集器,甚至根本不需要流。 Just use a composite sort: 只需使用复合排序:

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

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 . 作为一个offtopic,我会断言您改变数据结构以使用Lombok自动生成getters / setters / constructors https://projectlombok.org/features/Data You should also use a more generic naming conversion, ie remove the "stud_" prefix of attributes. 您还应该使用更通用的命名转换,即删除属性的“stud_”前缀。

import lombok.Data;
@Data
class Student {
    String id;
    String name;
    String location;
}

I was facing same issue and I tried all the solutions but not able to fix the problem, Then I tried following way and finally I am able to fix my problem. 我遇到了同样的问题,我尝试了所有解决方案,但无法解决问题,然后我尝试了以下方式,最后我能够解决我的问题。

In above example my studlist was already sorted but still its generating map in any order that was main question. 在上面的示例中,我的studlist已经排序,但仍然以任何顺序生成地图,这是主要问题。

Map<String, List<Student>> studlistGrouped =
    studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));
**studlistGrouped = new TreeMap<String, List<Student>>(studlistGrouped);**

So using this above solution it sorted all the keys in Map. 因此,使用上面的解决方案,它会对Map中的所有键进行排序 Our goal to get result into Map only then why people try to converting it into list? 我们的目标是将结果导入Map,然后人们才会尝试将其转换为列表?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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