简体   繁体   English

对对象列表进行排序和分组

[英]Sorting and Grouping on a list of objects

I have a List of Procedure objects as below我有一个程序对象列表如下

Procedure1  01/01/2020
Procedure2  03/01/2020
Procedure3  03/01/2020
Procedure1  04/01/2020
Procedure5  05/01/2020, 02/01/2020
Procedure2  06/01/2020

and my Procedure class is like我的程序类就像

Class Procedure {
    List<Date> procedureDate;
    String procedureName;
}

I want to sort and group the objects based on the below conditions.我想根据以下条件对对象进行排序和分组。

  1. All procedures should be grouped based on the procedure name.所有程序都应根据程序名称进行分组。
  2. Procedures must be in descending order of procedure date.程序必须按程序日期的降序排列。 [first element in date list ie, procedureDate.get[0]] [日期列表中的第一个元素,即procedureDate.get[0]]
  3. Same Procedures grouped together should be in descending order of Date.组合在一起的相同程序应按日期降序排列。

End result must be,最终结果一定是,

Procedure2  06/01/2020
Procedure2  03/01/2020

Procedure5  05/01/2020, 02/01/2020

Procedure1  04/01/2020
Procedure1  01/01/2020

Procedure3  03/01/2020

I was able to achieve this using Comparator and old java code.我能够使用 Comparator 和旧的 java 代码来实现这一点。 Is it possible to achieve the same using java8 streams, collectors and grouping by?是否可以使用 java8 流、收集器和分组来实现相同的目标?

This is a very interesting question.这是一个非常有趣的问题。 The solution is not as easy as it looks to be .解决方案并不像看起来那么容易 You have to divide the solution into multiple steps:您必须将解决方案分为多个步骤:

  1. Get the max value for each grouped procedureName based on the first dates in the List<Date> .根据List<Date>的第一个日期获取每个分组的procedureName的最大值。
  2. Compare the Procedure instances based on max Date value from the Map<String, Date created in the step one.根据第一步中创建的Map<String, Date中的最大Date值比较Procedure实例。
  3. If they are equal distinguish them by the name (ex. two times Procedure 2 ).如果它们相等,则通过名称区分它们(例如两次Procedure 2 )。
  4. If they are still equal, sort the Procedure instances based on their actual first date.如果它们仍然相等,则根据它们的实际第一个日期对Procedure实例进行排序。

Here is the demo at: https://www.jdoodle.com/iembed/v0/Te .这是演示: https : //www.jdoodle.com/iembed/v0/Te

Step 1第1步

List<Procedure> procedures = ...

Map<String, Date> map = procedures.stream().collect(
    Collectors.collectingAndThen(
        Collectors.groupingBy(
            Procedure::getProcedureName,
            Collectors.maxBy(Comparator.comparing(s -> s.getProcedureDate().get(0)))),
    s -> s.entrySet().stream()
        .filter(e -> e.getValue().isPresent())
        .collect(Collectors.toMap(
              Map.Entry::getKey,
              e -> e.getValue().get().getProcedureDate().get(0)))));

.. explained: There is a simple way to get a Procedure with maximum first date grouped by procedureName . ..解释说:有一个简单的方式来获得一个Procedure通过分组最大的第一次约会procedureName

Map<String, Optional<Procedure>> mapOfOptionalProcedures = procedures.stream()
    .collect(Collectors.groupingBy(
             Procedure::getProcedureName,
             Collectors.maxBy(Comparator.comparing(o -> o.getProcedureDate().get(0)))));

However, the returned structure is a bit clumsy ( Map<String, Optional<Procedure>> ), to make it useful and return Date directly, there is a need of additional downstream collector Collectors::collectingAndThen which uses a Function as a result mapper:但是,返回的结构有点笨拙( Map<String, Optional<Procedure>> ),为了使其有用并直接返回Date ,需要额外的下游收集器Collectors::collectingAndThen使用Function作为结果映射器:

Map<String, Date> map = procedures.stream().collect(
    Collectors.collectingAndThen(
        /* grouping part */,
        s -> s.entrySet().stream()
            .filter(e -> e.getValue().isPresent())
            .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    e -> e.getValue().get().getProcedureDate().get(0)))));

... which is effectively the first snippet. ...这实际上是第一个片段。

Steps 2, 3 and 4步骤 2、3 和 4

Basically, sort by the maximum date for each group.基本上,按每个组的最大日期排序。 Then sort by the name and finally by the actual first date.然后按名称排序,最后按实际的第一个日期排序。

Collections.sort(
    procedures,
    (l, r) -> {
        int dates = map.get(r.getProcedureName()).compareTo(map.get(l.getProcedureName()));
        if (dates == 0) {
             int names =  l.getProcedureName().compareTo(r.getProcedureName());
             if (names == 0) {
                 return r.getProcedureDate().get(0).compareTo(l.getProcedureDate().get(0));
             } else return names;
        } else return dates;
    }
);

Sorted result排序结果

Using the deprecated java.util.Date according to your question, the sorted procedures will have sorted items like your expected snippet (I have overrided the Procedure::toString method)根据您的问题使用已弃用的java.util.Date ,排序procedures将具有排序项目,如您预期的片段(我已经覆盖了Procedure::toString方法)

@Override
public String toString() {
     return procedureName + " " + procedureDate;
}
Procedure2 [Mon Jan 06 00:00:00 CET 2020]
Procedure2 [Fri Jan 03 00:00:00 CET 2020]
Procedure5 [Sun Jan 05 00:00:00 CET 2020, Thu Jan 02 00:00:00 CET 2020]
Procedure1 [Sat Jan 04 00:00:00 CET 2020]
Procedure1 [Wed Jan 01 00:00:00 CET 2020]
Procedure3 [Fri Jan 03 00:00:00 CET 2020]

My thought is coming from functional programming which is based on map-reduce.我的想法来自基于 map-reduce 的函数式编程。 You can see groupBy/collect is actually a form of reduce anyway and this problem can be better "merge" rather than using groupBy feature of Stream.你可以看到 groupBy/collect 实际上是一种 reduce 形式,这个问题可以更好地“合并”而不是使用 Stream 的 groupBy 功能。 This is my implementation in pure Stream.这是我在纯 Stream 中的实现。

List<Procedure> a = List.of(
    new Procedure(...),
    ...

)


List<Procedure> b = a.stream().map((p)-> {                    // Prepare for reduce by create Map for each object
        Map<String,Procedure> mapP = new HashMap<>();
        mapP.put(p.getProcedureName(),p)
        return mapP
    }).reduce((p,q)->{                                         //Use reduce to merge
        q.entrySet().stream().forEach((qq)-> {
            if (p.containsKey(qq.getKey())) {
                p.get(qq.getKey()).setProcedureDate(
                    new ArrayList<Date>(
                        Stream.concat(
                            p.get(qq.getKey()).getProcedureDate().stream(),
                            qq.getValue().getProcedureDate().stream())
                        .collect(Collectors.toSet()))
                );
            } else {
                p.put(qq.getKey(), qq.getValue());
            }

        })

        return p;
    }).get().values().stream().map(p-> {                          //sort date inside object
            p.setProcedureDate(p.getProcedureDate().stream().sorted().collect(Collectors.toList()))
            return p;
        }
    ).sorted((x,y)->                                         //sort object by the first date

        x.procedureDate.get(0).compareTo(y.procedureDate.get(0))

    ).collect(Collectors.toList());

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

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