【问题标题】:Java Lambda expression to avoid multiple iterations避免多次迭代的 Java Lambda 表达式
【发布时间】:2014-06-25 11:51:46
【问题描述】:

伙计们,

考虑以下示例,给定一个 Trade 对象列表,我的代码需要返回一个数组,其中包含 24 小时、7 天、30 天和所有时间的交易量。

使用普通的旧迭代器,这只需要对集合进行一次迭代。

我正在尝试使用 Java 8 流和 Lambda 表达式来做同样的事情。我想出了这段代码,它看起来很优雅,运行良好,但需要对列表进行 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};
}

我怎样才能在列表上只使用一次迭代来达到同样的效果?

【问题讨论】:

  • 总的问题是:为什么您只想在一次迭代中完成它?
  • 因为它更高效。
  • 不!这不是更有效!我问了这个问题,正如我所期望的那样。而且您的假设是不正确的:谁说每次执行一次操作的两次迭代一定比一次执行两次操作的迭代效率低?
  • 考虑我有 1,000,000 笔交易和 100 种不同的交易量要计算并且方法 trade.getTimestamp() 很昂贵的情况。使用迭代器我只需要调用它 1,000,000 次,而使用 lambda 我需要调用它 1 亿次。
  • 我建议您比较/测量性能,看看它有什么不同。将测试放在一起应该很容易。在哪种情况下使用循环可能会产生很大的不同,或者在哪种情况下使用您认为更清晰的方式可能会有所不同。

标签: java lambda java-8 java-stream


【解决方案1】:

这个问题类似于“汇总统计”收集器。看看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);
   }

   ...

}

它设计用于collect();这是IntStream.summaryStatistics()的实现

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

像这样编写Collector 的好处是您的自定义聚合可以并行运行。

【讨论】:

  • 这是我最初的方法,但请注意映射之前应用的不同过滤器。您将如何定义收集器并仍然应用过滤器?
  • accept 方法将有条件地增加各种总和。基本上,您通过收集器铲除所有数据,它可以挑选出它想要的位。对于顺序处理,这基本上等同于 for 循环,但它并行化干净,而 for 循环则不然。
【解决方案2】:

感谢 Brian,我最终实现了下面的代码,它并不像我希望的那么简单,但至少它只迭代一次,它的并行准备就绪并且它通过了我的单元测试。 欢迎任何改进意见。

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

【讨论】:

  • 您假定 getTimestamp was the expensive part, so you could factor that out within accept` 而不是执行三次。
  • 现在在您编辑之后,您将timestamptimestamp 进行比较,这是没有意义的。如果您将局部变量命名为与实例字段相同的名称,则必须使用this. 限定对后者的访问。请注意,您的条件是相关的:对于肯定的i,您可以说如果x+i&gt;ytrue,您知道x+7*i&gt;yx+30*i&gt;y 也是true(只要您可以排除溢出) )。或者如果x+30*i&gt;yfalsex+7*i&gt;yx+i&gt;y 也必须是false。所以还有改进条件代码的空间……
  • 已修复,我正在尝试将复杂代码简化为简化示例,从而导致这些问题。
【解决方案3】:

也许可以使用Collectors.groupingBy 方法对数据进行分区,但公式会很复杂,而且不会泄露意图。

由于getTimestamp() 是一项昂贵的操作,最好将其保留为Java 8 之前的迭代,这样您只需为每个Trade 计算一次值。

不要仅仅因为 Java 8 添加了闪亮的新工具,就试图把它变成一把锤子来敲钉子。

【讨论】:

    猜你喜欢
    • 2018-09-30
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    • 2020-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多