收藏家。组合成对象列表?

时间:2016-01-24 12:16:06

标签: java java-8 java-stream

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

其中Day有Hour对象列表,Hour有一个TimeEntry对象列表,表示它所代表的小时。

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

void processKey(Keys key)
{

    // check timing (keystrokes within 100 ms)
    TimeSpan elapsed = (DateTime.Now - _lastKeystroke);
    if (elapsed.TotalMilliseconds > 100)
        _barcode = ""; // Clear();

    // record keystroke & timestamp
    _barcode += (char)key; // e.KeyChar);
    _lastKeystroke = DateTime.Now;

    // process barcode

    if (key == Keys.Enter && _barcode.Length > 0)
    {
        string msg = new String(_barcode.ToArray());
        if (BarCodeScanned != null)
        {
            BarCodeScanned(sender, 
                           new BarcodeScannedEventArgs(new String(_barcode.ToArray())));
        }

        BarCodeScanned(_barcode);
        _barcode.Clear();
    }

}

如何使用Java 8s的流程执行此操作?

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

示例输入:

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

2 个答案:

答案 0 :(得分:2)

这是一项非常复杂的操作。直接的方法是首先通过分组来创建Map<Integer, Map<Integer, List<TimeEntry>>>,然后在几个小时内进行分组。获得该地图后,我们可以对其进行后处理以创建所需的List<Day>

创建临时地图非常简单。我们可以使用Collectors.groupingBy(classifier, downstream)分类器返回日期(通过方法引用TimeEntry::getDay),下游收集器是另一个groupingBy收集器,它在几小时内进行分类(通过方法引用{{1 }})。在此步骤之后,我们在每天都有一张地图,其中值是每小时映射到相应时间条目的地图。

接下来,我们需要做的是从该地图中制作TimeEntry::getHour。基本上,地图的每个条目(所以每天的数字)必须映射到相应的List<Day>对象。

  • Day只是条目的关键。
  • dayNr是当天和一小时的其中一个时间条目的日期名称。由于我们之前按这些字段进行分组,因此必须存在一次条目:为了检索它,我们得到该日期编号的小时图dayName并保留第一个值。
  • 可以通过操作该日期编号的小时图Map<Integer, List<TimeEntry>>来检索hours。对于该地图的每个条目:
    • Map<Integer, List<TimeEntry>>只是条目的关键
    • hourNr字段是条目
    • 的值
    • entries是该小时数的所有持续时间条目的添加。
  • 最后,sumOfEntries为该天数的每个小时编号添加了所有sumOfHours

完整的实现如下,假设您的所有域对象都有适当的构造函数和相应的getter:

sumOfEntries

请注意,从列表中添加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()

答案 1 :(得分:0)

好吧,我不能为你编写所有代码,但是这会实现Boris The Spider的想法,并希望能让你开始。在实例化TimeEntry列表之后(参见how to initialize static ArrayList in one line):

List<TimeEntry> timeEntries = new ArrayList<TimeEntry>() {
    {
        add(new TimeEntry("2016-01-22T10:00", "PT5M"));
        add(new TimeEntry("2016-01-23T08:00", "PT7M"));
        add(new TimeEntry("2016-01-23T08:43", "PT3M"));
    }
};

然后你stream它。您将需要两个groupingBy收藏家。第一个将按小时分组:

Collector<TimeEntry, ?, Map<Integer, List<TimeEntry>>> groupingByHours
    = Collectors.groupingBy(TimeEntry::getHour);

,第二个将按天分组。

Collector<TimeEntry, ?, Map<Integer, Map<Integer, List<TimeEntry>>>> groupingByDaysByHour
    = Collectors.groupingBy(TimeEntry::getDay, groupingByHours);

然后,您可以将您的TimeEntry列表按天分组,然后按小时分组:

Map<Integer, Map<Integer, List<TimeEntry>>> dayMap = 
    timeEntries.stream()
    .collect( groupingByDaysByHour);

这将给出输出:

{22={10=[2016-01-22T10:00 PT5M]}, 23={8=[2016-01-23T08:00 PT7M, 2016-01-23T08:43 PT3M]}}

为了完整起见,您修改了原始TimeEntry类以使用此示例:

class TimeEntry {
    private LocalDateTime start;
    private Duration duration;

    public TimeEntry(String start, String duration) {
        this.start = LocalDateTime.parse(start, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        this.duration = Duration.parse(duration);
    }

    public int getDay() {
        return start.get(ChronoField.DAY_OF_MONTH);
    }
    public int getHour() {
        return start.get(ChronoField.HOUR_OF_DAY);
    }
    public long getDuration() {
        return duration.toMinutes();
    }
    @Override
    public String toString() {
        return start.toString() + " " + duration.toString();
    }
}