Использование Java Stream для подсчета вхождений дат в списке элементов
У меня есть список элементов со свойством Date (java.util.), И я хочу создать DataSeriesItem для каждого дня, начиная с самой старой даты и по настоящее время. Это для серии графиков с временной шкалой.
Создание этого DataSeriesItem будет выглядеть так:
DataSeriesItem seriesItem = new DataSeriesItem(Date, occurrenceCount);
Где occurrenceCount
- это количество элементов, для которых их свойство Date соответствует этому дню. Первый параметр также может иметь тип java.time.Instant
Мне удалось найти способ, который работает, но я уверен, что мой подход очень плохой и может быть реализован одним потоком, может быть, двумя. Тем не менее, я новичок в потоках и не мог сделать это со своими знаниями.
Это возможно с потоком? Как это будет выглядеть примерно?
Я не прошу вас сделать всю мою реализацию заново, а лишь укажу на правильные функции потоков и сопоставления, которые вы бы использовали, и на бонусные очки - пример этого.
Вот мое уродливое решение:
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 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();
}
Ответы
Ответ 1
Вы можете использовать groupingBy()
и затем использовать коллектор downstream counting()
.
Map<Date, Long> occurrances = dateList.stream().collect(
groupingBy(d -> yourTransformation(d), counting()));
Должно быть достаточно легко создать ваши объекты DataSeriesItem
из этой карты.
Ответ 2
Считать, что вы ищете что-то вроде:
Map<Instant, Long> foundInstants = foundDates.stream()
.collect(Collectors.groupingBy(Date::toInstant, Collectors.counting()));
добавить к этому вы могли бы сократить те, если if..else
в:
ExtendedDataSeriesItem seriesItem =
new ExtendedDataSeriesItem(c.toInstant(), foundInstants.getOrDefault(c.toInstant(), 0L));
seriesItem.setSeriesType("singleDataPoint");
series.add(seriesItem);
и это говорит о том, что вам следует одновременно LocalDateTime
на LocalDateTime
и воздержаться от использования Date
.