Java 8中使用自定义逻辑的分组技巧

10

我有一个Records列表,其中包含两个字段:LocalDateTime instantDouble data

我想按小时对所有记录进行分组,并创建一个Map<Integer, Double>。 其中键(Integer)是小时,值(Double)是该小时的最后数据减去该小时的第一条数据。

目前为止我已经完成了以下工作:

Function<Record, Integer> keyFunc = rec->rec.getInstant().getHour();
Map<Integer, List<Record>> valueMap = records.stream().collect(Collectors.groupingBy(keyFunc));

我希望值映射表存储的是Double而不是List<Records>

例如:记录列表可以如下:

Instant            Data
01:01:24           23.7
01:02:34           24.2
01:05:23           30.2
...
01:59:27           50.2
02:03:23           54.4
02:04:23           56.3
...
02:58:23           70.3
...

结果地图应该是:

Key       Value
1          26.5 (50.2-23.7)
2          15.9 (70.3-54.4)
...

你能用一个例子来解释一下 values(Double) are last Data of that hour - first Data of that hour. 吗? - Ryuzaki L
请根据您在此处的评论中所述的确切期望编辑问题。 - Naman
3个回答

8

groupingBy 中,您主要寻找的是 Collectors.mapping

Map<Integer, List<Double>> valueMap = records.stream()
        .collect(Collectors.groupingBy(keyFunc, 
                Collectors.mapping(Record::getData, Collectors.toList())));

这将按照它们的“instant”小时将“Record”分组,并将相应数据作为映射值的列表。根据进一步的评论,我想从最后一个数据中减去第一个数据。是的,该列表将基于“instant”排序。您可以使用分组映射来获得所需的输出:
Map<Integer, Double> output = new HashMap<>();
valueMap.forEach((k, v) -> output.put(k, v.get(v.size() - 1) - v.get(0)));

或者,您可以使用Collectors.mappingCollectors.collectingAndThen进一步处理:

Map<Integer, Double> valueMap = records.stream()
        .collect(Collectors.groupingBy(keyFunc,
                Collectors.mapping(Record::getData, 
                        Collectors.collectingAndThen(
                                Collectors.toList(), recs -> recs.get(recs.size() - 1) - recs.get(0)))));

生成的映射可以是Map<Integer,Double>吗?该映射的值将是该小时的最后数据减去该小时的第一数据,以小时为键。 - Deb
“last data of that hour” 指的是什么?例如,现在每个小时都是一个唯一的键,那么上面列表中的“last data”是什么? - Naman
假设有10条记录是在某个小时内,比如第20个小时。因为即时字段是一个时间戳。第一条记录是20:01:36,最后一条记录是20:59:20,中间还有8条其他记录。我想要从第一条数据中减去最后一条数据。 - Deb
@nullpointer 你确定在计算差异之前不需要对 recs 进行排序吗? - ernest_k
@ernest_k 嗯,我依赖于原帖中的评论,即记录是基于时间戳排序的。 - Naman
显示剩余3条评论

4

您可以将collectingAndThen用作下游收集器以进行groupingBy,并使用每个组的两个极值来计算差异:

Map<Integer, Double> result = records.stream()
    .collect(
        Collectors.groupingBy(rec -> rec.getInstant().getHour(),

        Collectors.collectingAndThen(
                Collectors.toList(), 
                list -> {
                    //please handle the case of 1 entry only
                    list.sort(Comparator.comparing(Record::getInstant));

                    return list.get(list.size() - 1).getData() 
                           - list.get(0).getData();
                })));

Collectors.groupingBy(rec -> rec.getInstant().getHour()将条目按小时分组。 此处使用的Collectors.collectingAndThen将每小时的条目作为列表进行排序,然后找到两个极端元素之间的差异。


但是我需要的值是 lastRecord.getData() - firstRecord.getData()。 - Deb
@Deb 我明白了,我只是在评论那个问题。我会进行编辑。 - ernest_k

1

根据评论中列表将按时间戳排序的说法,以下内容可以正常工作:

    Map<Integer, Double> valueMap = records.stream()
            .collect(Collectors.groupingBy(rec -> rec.getInstant().getHour(),
                    Collectors.mapping(Record::getData,
                        Collectors.collectingAndThen(Collectors.toList(),recs -> recs.get(recs.size()-1) - recs.get(0)))));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接