10

I have a list of Records. Which has two fields: LocalDateTime instant and a Double data.

I want to groupBy all the records by Hour and create a Map<Integer, Double>. Where the keys (Integer) are hours and values(Double) are last Data of that hour - first Data of that hour.

What I have done so far is following:

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

I want the value map to hold Double instead of List<Records>.

For Example: List records can be following:

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
...

etc

Resulting map should be:

Key       Value
1          26.5 (50.2-23.7)
2          15.9 (70.3-54.4)
...
2
  • can you explain this with an example values(Double) are last Data of that hour - first Data of that hour.? Commented Dec 29, 2018 at 6:47
  • Please edit the question with your exact expectation as stated in the comment here. Commented Dec 29, 2018 at 6:55

3 Answers 3

8

You are mostly looking for Collectors.mapping within the groupingBy.

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

This would group Records by their instant's hour and corresponding data for such records into a List as values of the map. Based on comments further

I want to subtract the first data from the last data

Yes the list will be sorted list based on instant

you can use the grouped map to get the desired output as:

Map<Integer, Double> output = new HashMap<>();
valueMap.forEach((k, v) -> output.put(k, v.get(v.size() - 1) - v.get(0)));

Alternatively, you could use Collectors.mapping with Collectors.collectingAndThen further as:

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)))));
Sign up to request clarification or add additional context in comments.

8 Comments

Can the resulting map be Map<Integer,Double>? The value of that map would be last data of that hour - first data of that hour against hour as the key.
What do you mean exactly by last data of that hour? for e.g. every hour now is a unique key, what would be the "last data" in the list above?
Suppose there are 10 records of a particular hour say 20th hour. Because, instant field is a timestamp. The first being 20:01:36 last being 20:59:20 there are 8 other records in between. I want to substract the first data from the last data.
@Deb 1. If you group by an hour, why are you worried to subtract minutes/seconds based data? 2. Is it possible then for you to have a sorted list based on instant?
Yes the list will be sorted list based on instant and resulting Map would be something like: 1=>23.2 , 2=>30.4, 3=> 89.0..etc
|
4

You can use collectingAndThen as a downstream collector to groupingBy, and use the two extreme values of each group to compute the difference:

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() will group entries by hour. As used here, Collectors.collectingAndThen will take hourly entries as lists, sort every such list by the instant field then find the difference between the two extreme elements.

2 Comments

But I need lastRecord.getData() - firstRecord.getData() as value
@Deb I see. I was just commenting about that. Will edit.
1

Based on the comment that the list would be sorted on timestamp, the following would work :

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.