Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8, Complex groupingBy over time interval

Using Java 8 + JavaFX

I am trying to do some data analysis and output my results to an XY Chart. I've gotten my chart itself squared away, and it accepts a structure like:

ObservableList<Data<Timestamp, BigDecimal>>

My data is stored in a custom object, like so

public class funds{
    private long id;
    private String userName;
    private BigDecimal current;
    private Timestamp createdAt;

    ....constructors/getters/setters....
}

a funds object gets created for every user, by a recurring Task on a 1 minute interval.

I'd like to chart an hourly average sum of all funds.current. So I'd normally sum up each minute for all users, and then avg that for each hour. In MySQL I can do this:

SELECT FLOOR(UNIX_TIMESTAMP(created_at)/(60*60)) AS hour, AVG(current)
FROM (
    SELECT FLOOR(UNIX_TIMESTAMP(created_at)/60) AS minute, SUM(current) as current, created_at
    FROM funds
    GROUP BY minute
) as table1
GROUP BY hour;

But in this instance, I don't have direct SQL access, and must use java. I have tried looking at streams and the Collectors.groupingBy, but I cannot wrap my head around it enough to get what I want.

Also the Collectors seem to not have any functions for use with BigDecimal, which I need, as doubles are completely terrible for financial data.

Can anyone point me in the right direction?

like image 926
Jonathon Hoaglin Avatar asked Aug 03 '26 20:08

Jonathon Hoaglin


1 Answers

If you really don't want to use doubles, try this:

Map<Long, BigDecimal> hourAverages = funds.stream()
    //group by minute; as a result we have Map<Long, BigDecimal>
    .collect(toMap(f -> f.createdAt.getTime() / 6000, f -> f.current, BigDecimal::add))
    .entrySet().stream()
    //group by hours; here we use BigDecimal[] to store sum and count; 
    //as a result we have Map<Long, BigDecimal[]>
    .collect(groupingBy(e -> e.getKey() / 60,
            collectingAndThen(
                    reducing(new BigDecimal[] {BigDecimal.ZERO, BigDecimal.ZERO},
                        e -> new BigDecimal[]{e.getValue(), BigDecimal.ONE},
                        (a, b) -> new BigDecimal[]{a[0].add(b[0]), a[1].add(b[1])}),
                    //finally divide by count
                    a -> a[0].divide(a[1], BigDecimal.ROUND_UP))));
like image 84
Kirill Simonov Avatar answered Aug 06 '26 08:08

Kirill Simonov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!