Bright Java expression to avoid multiple iterations

People,

Consider the following example, given the list of Trade objects that I need to return a code containing the volume of trade for 24 hours, 7 days, 30 days and all the time.

Using a simple old iterator, this requires only one iteration over the collection.

I am trying to do the same using Java 8 and Lambda threads. I came up with this code that looks elegant, works great, but requires 4 iterations over the list:

public static final int DAY = 24 * 60 * 60; public double[] getTradeVolumes(List<Trade> trades, int timeStamp) { double volume = trades.stream().mapToDouble(Trade::getVolume).sum(); double volume30d = trades.stream().filter(trade -> trade.getTimestamp() + 30 * DAY > timeStamp).mapToDouble(Trade::getVolume).sum(); double volume7d = trades.stream().filter(trade -> trade.getTimestamp() + 7 * DAY > timeStamp).mapToDouble(Trade::getVolume).sum(); double volume24h = trades.stream().filter(trade -> trade.getTimestamp() + DAY > timeStamp).mapToDouble(Trade::getVolume).sum(); return new double[]{volume24h, volume7d, volume30d, volume}; } 

How can I achieve the same using only one iteration through the list?

+7
java lambda java-8 java-stream
source share
3 answers

This problem is similar to the "summary statistics" collector. Take a look at the IntSummaryStatistics class:

 public class IntSummaryStatistics implements IntConsumer { private long count; private long sum; ... public void accept(int value) { ++count; sum += value; min = Math.min(min, value); max = Math.max(max, value); } ... 

}

It is designed to work with collect() ; here is the implementation of IntStream.summaryStatistics()

 public final IntSummaryStatistics summaryStatistics() { return collect(IntSummaryStatistics::new, IntSummaryStatistics::accept, IntSummaryStatistics::combine); } 

The advantage of writing a Collector like this is that your user aggregation can work in parallel.

+9
source share

Thanks Brian, I finished the implementation of the code below, it is not as easy as I had hoped, but at least it iterates only once, its parallel readiness and passes my unit tests. Any ideas for improvements are welcome.

 public double[] getTradeVolumes(List<Trade> trades, int timeStamp) { TradeVolume tradeVolume = trades.stream().collect( () -> new TradeVolume(timeStamp), TradeVolume::accept, TradeVolume::combine); return tradeVolume.getVolume(); } public static final int DAY = 24 * 60 * 60; static class TradeVolume { private int timeStamp; private double[] volume = new double[4]; TradeVolume(int timeStamp) { this.timeStamp = timeStamp; } public void accept(Trade trade) { long tradeTime = trade.getTimestamp(); double tradeVolume = trade.getVolume(); volume[3] += tradeVolume; if (!(tradeTime + 30 * DAY > timeStamp)) { return; } volume[2] += tradeVolume; if (!(tradeTime + 7 * DAY > timeStamp)) { return; } volume[1] += tradeVolume; if (!(tradeTime + DAY > timeStamp)) { return; } volume[0] += tradeVolume; } public void combine(TradeVolume tradeVolume) { volume[0] += tradeVolume.volume[0]; volume[1] += tradeVolume.volume[1]; volume[2] += tradeVolume.volume[2]; volume[3] += tradeVolume.volume[3]; } public double[] getVolume() { return volume; } } 
+1
source share

Perhaps you can use the Collectors.groupingBy method to separate the data, but the equation will be complex and inappropriate.

Since getTimestamp() is an expensive operation, it is best to save it as an iteration until Java 8, so you only need to calculate the value once per Trade .

Just because Java 8 adds brilliant new tools, don’t try to turn it into a hammer to hammer all the nails.

0
source share

All Articles