【问题标题】:Using Java Stream to count occurrences of Dates in a list of items使用 Java Stream 计算项目列表中日期的出现次数
【发布时间】:2019-06-06 10:50:18
【问题描述】:

我有一个具有 (java.util.)Date 属性的项目列表,我想从最早的日期到现在创建一个每天的 DataSeriesItem。它适用于带有时间线的图表系列。

DataSeriesItem 的创建将如下所示:
DataSeriesItem seriesItem = new DataSeriesItem(Date, occurrenceCount);
其中occurrenceCount 是其日期属性与当天匹配的项目数。第一个参数的类型也可以是java.time.Instant

我设法找到了一种可行的方法,但我确信我的方法非常很糟糕,可能只需要一个流,也可能是两个。但是,我是流的初学者,以我的知识无法做到。

这可以通过流实现吗? 大概会是什么样子?
我并不是要您重新执行我的整个实现,而只是向我指出您将使用的正确的流函数和映射,并作为奖励积分的示例。

这是我丑陋的解决方案:

List<?> items = myItems;
Collection<Date> foundDates = new HashSet<>();

for (Object item : items) {
    foundDates.add((Date)getPropertyValueFromItem(item, configurator.getDateProperty()));
}

//======  This is the part I am asking about ======//

Map<Instant, Integer> foundInstants = new HashMap<>();
foundDates.stream().sorted(Date::compareTo).forEach(date -> {
    Calendar c = Calendar.getInstance();
    c.clear(); // clear nanoseconds, or else equals won't work!
    c.set(date.getYear()+1900, date.getMonth(), date.getDate(), 0, 0, 0);
    if(!foundInstants.containsKey(c.toInstant())){
        foundInstants.put(c.toInstant(), 1);
    } else {
        // increment count of that entry
        Integer value = foundInstants.get(c.toInstant());
        foundInstants.remove(c.toInstant());
        foundInstants.put(c.toInstant(), ++value);
    }
});

//====== Leaving this code here for context ======//  
// Could this maybe simplyfied too by using streams  ?

// find oldest date
Date dateIndex = foundDates.stream().min(Date::compareTo).get();
Date now = new Date();

// starting from oldest date, add a seriesItem for each day until now
// if dateOccurrences contains the current/iterated date, use it's value, else 0
while(dateIndex.before(now)){
    Calendar c = Calendar.getInstance();
    c.clear();// clear nanoseconds, or else equals won't work!
    c.set(dateIndex.getYear()+1900, dateIndex.getMonth(), dateIndex.getDate(), 0, 0, 0);

    if(foundInstants.containsKey(c.toInstant())){
        ExtendedDataSeriesItem seriesItem = new ExtendedDataSeriesItem(c.toInstant(), foundInstants.get(c.toInstant()));
        seriesItem.setSeriesType("singleDataPoint");
        series.add(seriesItem);
    } else {
        ExtendedDataSeriesItem seriesItem = new ExtendedDataSeriesItem(c.toInstant(), 0);
        seriesItem.setSeriesType("singleDataPoint");
        series.add(seriesItem);
    }
    c.add(Calendar.DATE, 1); // adding a day is complicated. Calendar gets it right. Date does not. This is why I don't use Date here
    dateIndex = c.getTime();
}

【问题讨论】:

  • 你为什么使用java.util.Date,你显然是在支持java.time.LocalDate的Java 8上?
  • @daniu 我没有做模型。此外,与这个问题完全无关
  • 您的问题中的series 是什么?
  • @nullpointer 它是一个 DataSeries (com.vaadin.flow.component.charts.model.DataSeries)

标签: java datetime java-8 java-stream


【解决方案1】:

您可以使用groupingBy(),然后使用下游收集器counting()。

Map<Date, Long> occurrances = dateList.stream().collect(
                  groupingBy(d -> yourTransformation(d), counting()));

从该地图创建 DataSeriesItem 对象应该很容易。

【讨论】:

  • 这似乎正是我想要的,非常感谢。
  • 像魅力一样工作(我必须写 Collectors.groupingBy 否则它不会识别它)。我基本上将this 用于yourTransformation(d),除了在末尾将其转换为日期而不是长。
  • @Cashbee 您可以使用静态导入,因此您不需要Collectors.。我这样做是因为它更具可读性,并且在此处的答案中也是如此。
【解决方案2】:

计算你正在寻找类似的东西:

Map<Instant, Long> foundInstants =  foundDates.stream()
            .collect(Collectors.groupingBy(Date::toInstant, Collectors.counting()));

除此之外,您可以将if..else 缩短为:

ExtendedDataSeriesItem seriesItem = 
        new ExtendedDataSeriesItem(c.toInstant(), foundInstants.getOrDefault(c.toInstant(), 0L));
seriesItem.setSeriesType("singleDataPoint");
series.add(seriesItem);

也就是说,您应该同时寻求迁移到LocalDateTime 并避免使用Date。

【讨论】:

  • 这看起来很有希望,似乎正是我想要的。我需要时间来测试它。大牛首先给出了相同的答案,所以滴答声传给了他。不过还是谢谢! @Lino nullpointer 是对的 :)
  • @Cashbee 没关系,还缩短了你的if..else
  • 在这一天试图将 Date 转换为 Calendar 为 Instant 只是因为 Date 不能执行 addDays(1) 之后,我无法继续使用 Date :)
猜你喜欢
  • 1970-01-01
  • 2016-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-24
  • 1970-01-01
  • 1970-01-01
  • 2011-07-02
相关资源
最近更新 更多