Reputation:
I was wondering is there is a way to use stream to get a single T
object from a List<T>
with averages for double values and min for date. I have like 20 properties and maybe there is also a way to go through all of them automatically and with an if the property is a double to get an average, if is a date to get a min?
id date value
3470, 2018-11-15 08:10:00+02, 25,101610.0234375
3467, 2018-11-15 07:53:00+02, 33,101398.984375
3468, 2018-11-15 07:54:00+02, 25,101599.765625
3549, 2018-12-28 18:20:00+02, 29.21
3550, 2018-12-28 18:24:00+02, 29.21
3551, 2018-12-28 18:27:00+02, 42.21
3552, 2019-01-07 09:42:00+02,
3553, 2019-01-07 09:50:00+02, 15.140000343323
3554, 2019-01-07 09:52:00+02, -1.3799999952316
3555, 2019-01-07 10:03:00+02, 14.949999809265
Upvotes: 0
Views: 115
Reputation: 23829
I think the most efficient and clean solution is to use the Stream::reduce
method and define a customer reduce function that performs min
for specific fields and average
for other fields depending on what you need.
list.stream().reduce(initialValue, (accumulator, listElement) -> reduceFunction(accumulator, listElement))
There's a slight difficulty here in that you need to know the length of the list to compute the average. So in order for the reduce function to be generic, the accumulator class has to have an element counter.
Upvotes: 0
Reputation: 3134
take a look at teeing collector which was introduced in Java 12. In worst case you can copy/paste the implementation (as was done here)
Result result = stream.collect(Collectors.teeing(
Collectors.mapping(T::getDate, Collectors.minBy(Comparator.naturalOrder())),
Collectors.averagingDouble(T::getValue),
Result::new
));
where result is a class defined by you
class Result {
Optional<LocalDate> minDate;
double avg;
//all args constructor
}
Upvotes: 1
Reputation: 26056
From your description the only thing that pops to my head is reflection.
You can also define a downstream function:
BinaryOperator<T> downstream = (t1, t2) -> new T(t1.getValue() + t2.getValue(), ..., t1.getDate().isBefore(t2.getDate()) ? t1.getDate() : t2.getDate());
And then reduce:
T aggregate = list.stream().reduce(downstream).get();
T result = new T(aggregate.getValue() / list.size(), aggregate.getDate(), ...)
P.S.1: Note, that this has quite an overhead as it creates a new object in BinaryOperator
, so this might not be the best solution when list is large.
P.S.2: This is a draft, I assumed you're using LocalDateTime
to store date
field, this is dependent on what you're using.
P.S.3: You need to define an aggregation method for your fields, for example id
.
P.S.4: As @Aaron pointed out - I'm calculating the sum for value
field, so that I can then divide it by the length of result, this way the average is correctly calculated.
Upvotes: 2