简体   繁体   English

Java Lambda表达式避免多次迭代

[英]Java Lambda expression to avoid multiple iterations

Folks, 伙计们,

Consider the following example, given a list of Trade objects my code needs to return an array containing trade volume for 24 hours, 7 days, 30 days and all times. 考虑以下示例,给定一个Trade对象列表,我的代码需要返回一个包含交易量24小时,7天,30天和所有时间的数组。

Using plain old iterator this requires only a single iteration over the collection. 使用普通的旧迭代器,这只需要对集合进行一次迭代。

I'm trying to do the same using a Java 8 streams and Lambda expressions. 我正在尝试使用Java 8流和Lambda表达式来做同样的事情。 I came up with this code, which looks elegant, works fine, but requires 4 iterations over the list: 我提出了这个代码,它看起来很优雅,工作正常,但需要在列表上进行4次迭代:

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 a single iteration over the list ? 如何在列表中仅使用一次迭代来实现相同的目标?

This problem is similar to the "summary statistics" collector. 此问题类似于“摘要统计信息”收集器。 Take a look at the IntSummaryStatistics class: 看看IntSummaryStatistics类:

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() ; 它旨在与collect() ; here's the implementation of IntStream.summaryStatistics() 这是IntStream.summaryStatistics()的实现

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

The benefit of writing a Collector like this is then your custom aggregation can run in parallel. 编写像这样的Collector的好处是您的自定义聚合可以并行运行。

Thanks Brian, I ended up implementing the code below, it's not as simple as I hoped but at least it iterates only once, its parallel ready and it passes my unit tests. 谢谢Brian,我最终实现了下面的代码,它并不像我希望的那样简单,但至少它只迭代一次,它的并行就绪,它通过我的单元测试。 Any improvements ideas are welcomed. 欢迎任何改进的想法。

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;
    }
}

It might be possible to use a Collectors.groupingBy method to partition the data however the equation would be complicated and not intent revealing. 也许可以使用Collectors.groupingBy方法对数据进行分区,但是方程式会很复杂而且没有意图揭示。

Since getTimestamp() is an expensive operation, it is probably best to keep it as a pre-Java 8 iteration so you only have to calculate the value once per Trade . 由于getTimestamp()是一项昂贵的操作,因此最好将其保留为Java 8之前的迭代,因此您只需按每次Trade计算一次值。

Just because Java 8 adds shiny new tools, don't try to turn it into a hammer to hammer in all nails. 仅仅因为Java 8添加了闪亮的新工具,不要试图将它变成锤子来锤击所有钉子。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM