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

基于以下答案: https : //stackoverflow.com/a/30202075/8760211

如何通过stud_id对每个组进行排序,然后通过stud_location返回一个包含所有学生的List作为分组结果,然后按stud_id排序?

将它作为现有Lambda表达式的扩展会很棒:

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

我需要根据原始列表中元素的顺序进行分组。

 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" 

结果将如下所示:

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

我尝试过以下方法:

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

但这不起作用。

如果我找到了你,你需要一个List (不是地图),其中学生按其位置分组, 并按组内的ID排序其中组也按ID排序,而不是按位置名称排序。 这是可能的,但需要一次分组和两次排序:

 //first, use your function to group students Map> studlistGrouped = students.stream() .collect(Collectors.groupingBy(Student::getLocation, Collectors.toList())); //then sort groups by minimum id in each of them List 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()); 

这将产生以下结果:

 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'} 

也许这可以更优雅地完成,欢迎提出建议

编辑:在写这个答案的时候,没有提到基于 OPs问题中原始列表元素顺序的分组 。 所以我的假设是按ID分类列表和组。 对于基于原始列表中的顺序的解决方案,请参阅其他答案,例如,Holgers one

由于结果应该是一个列表,因此您不是分组而是简单排序(根据定义的规则更改顺序)。 主要障碍是您希望在原始列表中首次遇到位置后对其进行排序。

直接的方法是首先修复此位置顺序,然后执行单个排序操作:

 Map 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)); 

如果您不能或不想修改原始列表,您只需使用副本:

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

也可以通过分组操作来解决这个问题,但这并不容易:

 List 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()))); 

请注意,您必须收集到LinkedHashMap以确保保留组的顺序。

您可以添加一行:

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

或者你可以写自己的collections家。

我知道我会选择哪一个。

首先尝试排序,然后分组,它运行良好。 以下代码对学生在该位置进行排序。

students.stream().sorted().collect(Collectors.groupingBy(Student::getLocation))
这种情况下的输出是

 {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]} 

如果您希望对位置进行排序,请使用下面的代码段

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

在这种情况下的输出是{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类实现Comparable,compareTo方法基于id。

首先,关于每组内的排序。 Collectors.groupingBy有第二个变体,它允许您指定用于生成组的收集器。 您指定的收集器可以是以排序方式收集项目的收集器(例如TreeSet ),并且作为完成操作将其转换为排序列表。 可以使用Collectors.collectingAndThen()创建这样的收集Collectors.collectingAndThen()

例如,我尝试使用Integers:

 List 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 

输出:

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

我确定你设法将此翻译成你的情况。 您可能需要使用自定义比较器创建一个TreeSet ,以便按照您希望的方式对每个组中的学生进行排序,或者,如果学生始终以相同的方式排序,则使Student实现Comparable

其次是关于分组。 Collectors.groupingBy默认创建一个HashMap ,它没有指定的键顺序(上面,键是按顺序正确排序的)。 因此,为了对键进行排序,您需要使用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 

我为地图指定了一个自定义比较器,以显示排序确实不同。 结果是:

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

现在,这是否比旧的Java 8之前的解决方案更具可读性和可维护性是一个品味问题……

如果您只想分组和排序,则不需要groupingBy()收集器,甚至根本不需要流。 只需使用复合排序:

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

不是100%清楚你是否期望一个Map>或者只是一个List ,不过这里有两个解决方案:

import:

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

检索Map> ,其中每个List包含按其ID排序的学生。

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

另一方面,如果您只想检索按给定属性排序的Student对象列表,那么执行groupingBysortedcollect并以某种方式将映射值减少到单个列表将浪费资源。 而只是在列表中对Student对象进行排序,提供排序键即ie

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

要么

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

或者根据你是否想要按多个属性排序,那么你可以做一些像shmosel的回答 。

您不需要按位置分组,因为输入和输出具有相同的数据类型。 您可以链接多个比较器并对输入进行排序。

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

作为一个offtopic,我会断言您更改数据结构以使用Lombok自动生成getters / setters / constructors https://projectlombok.org/features/Data 。 您还应该使用更通用的命名转换,即删除属性的“stud_”前缀。

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

我遇到了同样的问题,我尝试了所有解决方案,但无法解决问题,然后我尝试了以下方式,最后我能够解决我的问题。

在上面的示例中,我的studlist已经排序,但仍然以任何顺序生成地图,这是主要问题。

 Map> studlistGrouped = studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location)); **studlistGrouped = new TreeMap>(studlistGrouped);** 

因此,使用上面的解决方案,它会对Map中的所有键进行排序 我们的目标是将结果导入Map,然后人们才会尝试将其转换为列表?