繁体   English   中英

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

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

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

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

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

Map<String, List<Student>> 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<Student> 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<Student> (不是地图),其中学生按其位置分组, 并按组内的ID排序,组中的组也按ID排序,而不是按位置名称排序。 这是可能的,但需要一个分组和两个排序:

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

这将产生以下结果:

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

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

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

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

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

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

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

进口:

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

检索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())))));

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

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

要么

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

或者根据您是否想要按多个属性排序,那么您可以执行类似shmosel的回答

您可以添加一行:

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

或者你可以写自己的收藏家。

我知道我会选择哪一个。

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

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

输出:

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

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

List<Student> 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<String, List<Student>> studlistGrouped =
    studlist.stream().collect(Collectors.groupingBy(w -> w.stud_location));
**studlistGrouped = new TreeMap<String, List<Student>>(studlistGrouped);**

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

暂无
暂无

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

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