Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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();
}
like image 960
kscherrer Avatar asked Jan 11 '19 13:01

kscherrer


1 Answers

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.

like image 140
daniu Avatar answered Oct 25 '22 18:10

daniu