Reputation: 5766
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
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
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