kscherrer
kscherrer

Reputation: 5766

Using Java Stream to count occurrences of Dates in a list of items

I have a List of items with a (java.util.)Date property, and I want to create a DataSeriesItem for each day beginning from the oldest date up to now. It is for a chart series with a timeline.

The creation of that DataSeriesItem will look like this:
DataSeriesItem seriesItem = new DataSeriesItem(Date, occurrenceCount);
Where the occurrenceCount is the count of Items where their Date property matches that day. The first parameter can also be of type java.time.Instant

I have managed to find a way that works, but I am certain that my approach is very bad and could possibly be done with one stream, maybe two. However, I am a beginner in streams and could not do it with my knowledge.

Is this possible with stream? How would it probably look like approximately?
I'm not asking you to actually do my whole implementation anew, but only point me to the correct streamfunctions and mappings that you would use, and for bonus points an example of it.

Here is my ugly solution:

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

Upvotes: 11

Views: 2376

Answers (2)

Naman
Naman

Reputation: 31888

To count you're looking for something like:

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

to add to that you could cut short those if..else into :

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

and this goes by saying that you should at the same time look for migrating to LocalDateTime and refrain from using Date.

Upvotes: 5

daniu
daniu

Reputation: 14999

You can use groupingBy() and then use the downstream collector counting().

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

It should be easy enough to create your DataSeriesItem objects from that map.

Upvotes: 9

Related Questions