给定一个TimeEntry对象的列表,我想将它们分为Day和Hour对象。

其中Day具有Hour对象的列表,而Hour具有其所代表的小时的TimeEntry对象的列表。

“日期”和“小时”还保留各自列表中TimeEntry持续时间的总和。如果可能,还可以将日期名称输入Day。 TimeEntry列表按“开始”排序。

public class TimeEntry {
  private LocalDateTime start;
  private Duration duration;

  public Integer getDay() { return this.start.get(ChronoField.DAY_OF_MONTH); }
  public Integer getHour() { return this.start.get(ChronoField.HOUR_OF_DAY); }
  public String getDayName() {
        return this.start.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault());
    }
}

public class Day {
  private Integer dayNr;
  private String dayName;
  private Duration sumOfHours;
  private List<Hour> hours;
}

public class Hour {
  private Integer hourNr;
  private Duration sumOfEntries;
  private List<TimeEntry> entries;
}


您如何使用Java 8s流?

List<Day> days = timeEntries.stream().collect(Collectors.groupingBy(...?


输入样例:

List<TimeEntry> timeEntries = [{"2016-01-22 10:00", "5 minutes"},
{"2016-01-23 08:00", "7 minutes"} , {"2016-01-23 08:43", "3 minutes"}]


样本输出:

[
{dayNr: 22, dayName: Friday, sumofEntries: 5 minutes
  [{hourNr: 10, sumofEntries: 5 minutes},
    [{"2016-01-22 10:00", "5 minutes"}]} ]},
{dayNr: 23, dayName: Saturday, sumofEntries: 10 minutes
  [{hourNr: 8, sumofEntries: 10 minutes},
    [{"2016-01-23 08:00", "7 minutes"},
     {"2016-01-23 08:43", "3 minutes"} ]}
]

最佳答案

这是一个相当复杂的操作。直接的方法是先通过按天分组,然后按小时分组来创建Map<Integer, Map<Integer, List<TimeEntry>>>。有了该映射后,我们可以对其进行后处理以创建所需的List<Day>

创建临时地图非常容易。我们可以使用Collectors.groupingBy(classifier, downstream),其中分类器返回日期(通过方法引用TimeEntry::getDay),而下游收集器是另一个groupingBy收集器,它按小时分类(通过方法引用TimeEntry::getHour)。完成此步骤后,我们将获得每天的地图,其中值是每小时映射到相应时间条目的地图。

接下来,我们需要做的是在该地图上制作一个List<Day>。基本上,地图的每个条目(因此每天的编号)都必须映射到相应的Day对象。


dayNr只是条目的键。
dayName是该日期和一个小时的时间条目之一的日期名称。自从我们较早地对这些字段进行分组以来,必须存在一个时间条目:要进行检索,我们将获得该天数的小时图Map<Integer, List<TimeEntry>>并仅保留第一个值。
可以通过操作该日号的小时图hours来检索Map<Integer, List<TimeEntry>>。对于该地图的每个条目:


hourNr只是条目的键
entries字段是条目的值
sumOfEntries是该小时数的所有时间条目持续时间的加法。

最后,sumOfHours是该日期编号的每个小时编号的所有sumOfEntries的加法。


下面是一个完整的实现,其中假定所有域对象都具有适当的构造函数和适当的getter:

public static void main(String[] args) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
    List<TimeEntry> timeEntries = Arrays.asList(
        new TimeEntry(LocalDateTime.parse("2016-01-22 10:00", formatter), Duration.ofMinutes(5)),
        new TimeEntry(LocalDateTime.parse("2016-01-23 08:00", formatter), Duration.ofMinutes(7)),
        new TimeEntry(LocalDateTime.parse("2016-01-23 08:43", formatter), Duration.ofMinutes(3))
    );

    Map<Integer, Map<Integer, List<TimeEntry>>> map =
        timeEntries.stream()
                   .collect(groupingBy(TimeEntry::getDay, groupingBy(TimeEntry::getHour)));

    List<Day> output =
        map.entrySet()
           .stream()
           .map(e -> {
              String dayName = e.getValue().values().iterator().next().get(0).getDayName();
              List<Hour> hours =
                 e.getValue().entrySet()
                             .stream()
                             .map(he -> new Hour(he.getKey(), sumDuration(he.getValue(), TimeEntry::getDuration), he.getValue()))
                             .collect(toList());
              return new Day(e.getKey(), dayName, sumDuration(hours, Hour::getSumOfEntries), hours);
           })
           .collect(toList());

    System.out.println(output);
}

private static <T> Duration sumDuration(List<T> list, Function<T, Duration> function) {
    return list.stream().map(function::apply).reduce(Duration.ofMinutes(0), (d1, d2) -> d1.plus(d2));
}


请注意,列表中Duration对象的添加已被纳入辅助方法sumDuration中。

上面示例的输出(假设toString()打印所有包含在方括号中的字段):

[Day [dayNr=22, dayName=vendredi, sumOfHours=PT5M, hours=[Hour [hourNr=10, sumOfEntries=PT5M, entries=[TimeEntry [start=2016-01-22T10:00, duration=PT5M]]]]], Day [dayNr=23, dayName=samedi, sumOfHours=PT10M, hours=[Hour [hourNr=8, sumOfEntries=PT10M, entries=[TimeEntry [start=2016-01-23T08:00, duration=PT7M], TimeEntry [start=2016-01-23T08:43, duration=PT3M]]]]]]

09-11 19:22