I come asking a question that I feel has not been exactly asked before, and it might produce some interesting answers :)
I am currently working on some Java code that has as goal:
- Receive list of
Collection<ForecastPerDate>
(see below) - Find items that have date >= today
- Get the "value" of the item with date closest to today (minimum diff)
- Floor it and round it
- If no data has been found it should fallback to 0 with a log message
public record ForecastPerDate(String date, Double value) {}
My implementation so far seems pretty efficient and sane to me, but I don't like mutating variables or state (I am becoming more of a Haskell dev lately haha) and always quite liked using the Streams API of Java.
Just FYI the project uses Java 17 so that helps. I assume this probably can be solved with a reduce()
function and some accumulator but I am unclear on how to, at least without causing more than one iteration.
Here is the code:
@Override
public Long getAvailabilityFromForecastData(final String fuCode,
final String articleCode,
final Collection<ForecastPerDate> forecasts) {
if (forecasts == null || forecasts.isEmpty()) {
log.info(
"No forecasts received for FU {} articleCode {}, assuming 0!",
fuCode,
articleCode
);
return 0L;
}
final long todayEpochDay = LocalDate.now().toEpochDay();
final Map<String, Double> forecastMap = new HashMap<>();
long smallestDiff = Integer.MAX_VALUE;
String smallestDiffDate = null;
for (final ForecastPerDate forecast : forecasts) {
final long forecastEpochDay = LocalDate.parse(forecast.date()).toEpochDay();
final long diff = forecastEpochDay - todayEpochDay;
if (diff >= 0 && diff < smallestDiff) {
// we look for values in present or future (>=0)
smallestDiff = diff;
smallestDiffDate = forecast.date();
forecastMap.put(forecast.date(), forecast.value());
}
}
if (smallestDiffDate != null) {
final Double wantedForecastValue = forecastMap.get(smallestDiffDate);
if (wantedForecastValue != null) {
return availabilityAmountFormatter(wantedForecastValue);
}
}
log.info(
"Resorting to fallback for FU {} articleCode {}, 0 availability for article! Forecasts: {}",
fuCode,
articleCode,
forecasts
);
return 0L;
}
private Long availabilityAmountFormatter(final Double raw) {
return Math.round(Math.floor(raw));
}
Thanks in advance! Look forward to learning and constructive feedback!
CodePudding user response:
Most of the operations you mention are available method-calls on streams.
- Receive list of Collection ->
forecasts.stream()
- Find items that have date >= today ->
.filter()
- Get the "value" of the item with date closest to today (minimum diff) ->
.min()
, giving anOptional<ForecastPerDate>
- Floor it and round it ->
optional.map()
- If no data has been found it should fallback to 0 with a log message ->
optional.orElseGet()
Put together, it would be something like this (I haven't compiled it, so it probably won't work on the first try):
@Override
public Long getAvailabilityFromForecastData(final String fuCode,
final String articleCode,
final Collection<ForecastPerDate> forecasts) {
var today = LocalDate.now();
return forecasts.stream()
.filter(forecast -> !today.isBefore(LocalDate.parse(forecast.date())))
.min(Comparator.comparing(forecast ->
Duration.between(today, LocalDate.parse(forecast.date()))
.map(forecast -> availabilityAmountFormatter(forecast.value()))
.orElseGet(() -> {
log.info("No forecasts found");
return 0L;
});
}
I would move some of the logic into ForecastPerDate
to avoid having to parse forecast.date()
multiple times.
CodePudding user response:
Here is one approach.
- stream the collection of objects
- filter out any dates older than and including today.
- then collect using
Collectors.minBy
and a special comparator. - then use the result with the rest of your code to either return the value or log the result.
public Long getAvailabilityFromForecastData(final String fuCode,
final String articleCode,
final Collection<ForecastPerDate> forecasts) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("M/d/yyyy");
Comparator<ForecastPerDate> comp =
Comparator.comparing(f -> LocalDate.parse(f.date(), dtf),
(date1, date2) -> date1.compareTo(date2));
Optional<ForecastPerDate> result = forecasts.stream()
.filter(fpd -> LocalDate.parse(fpd.date(),dtf)
.isAfter(LocalDate.now()))
.collect(Collectors.minBy(comp));
if (result.isPresent()) {
return availabilityAmountFormatter(result.get().value());
}
log.info(
"Resorting to fallback for FU {} articleCode {}, 0 availability for article! Forecasts: {}",
fuCode, articleCode, forecasts);
return 0L;
}
Demo
Note: for this answer and demo I included in the above method a DateTimeFormatter
since I don't know the format of your dates. You will probably need to alter it for your application.
List<ForecastPerDate> list = List.of(
new ForecastPerDate("6/14/2022", 112.33),
new ForecastPerDate("6/19/2022", 122.33),
new ForecastPerDate("6/16/2022", 132.33),
new ForecastPerDate("6/20/2022", 142.33));
long v = getAvailabilityFromForecastData("Foo","Bar", list);
System.out.println(v);
prints
132 (based on current day of 6/15/2022)
If no dates are present after the current day, 0
will be returned and the issue logged.
CodePudding user response:
If you want to keep a similar flow to what you have, try something like this:
final LocalDate now = LocalDate.now();
return forecasts.stream()
.map(ForecastPerDate::date)
.map(LocalDate::parse)
.filter(fd -> fd.isEqual(now) || fd.isAfter(now))
.min()
.map(LocalDate::toString)
.map(forecastMap::get)
.map(this::availabilityAmountFormatter)
.orElseGet(() -> {
log.info("Resorting to fallback...");
return 0L;
});