user7188242
user7188242

Reputation:

Return averages from a List<T> as a single T object

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

Answers (3)

nimrodm
nimrodm

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

Adrian
Adrian

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

Andronicus
Andronicus

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

Related Questions