我写了第一个Project Euler问题的答案:
添加1000以下的所有自然数,即3或5的倍数。
我遇到的第一件事是:
(1 until 1000).filter(i => (i % 3 == 0 || i % 5 == 0)).foldLeft(0)(_ + _)
但它很慢(需要125毫秒),所以我重写了它,只是想到'另一种方式'而不是'更快的方式'
(1 until 1000).foldLeft(0){
(total, x) =>
x match {
case i if (i % 3 == 0 || i % 5 ==0) => i + total // Add
case _ => total //skip
}
}
这要快得多(仅2毫秒)。为什么?我猜第二个版本只使用Range生成器,并没有以任何方式显示完全实现的集合,一次完成所有这一切,更快,内存更少。我是对的吗?
这里是IdeOne上的代码:http://ideone.com/GbKlP
答案 0 :(得分:24)
正如其他人所说,问题是filter
创建了一个新的集合。备选withFilter
没有,但没有foldLeft
。此外,使用.view
,.iterator
或.toStream
都可以避免以各种方式创建新集合,但它们在这里比您使用的第一种方法慢,我觉得有点奇怪第一
但是,然后......看,1 until 1000
是Range
,其大小实际上非常小,因为它不存储每个元素。此外,Range
的{{1}}已经过极优化,甚至是foreach
,而其他任何集合都不是这种情况。由于specialized
是foldLeft
实现的,只要您使用foreach
,就可以享受优化方法。
Range
:
(_: Range).foreach
@inline final override def foreach[@specialized(Unit) U](f: Int => U) {
if (length > 0) {
val last = this.last
var i = start
while (i != last) {
f(i)
i += step
}
f(i)
}
}
(_: Range).view.foreach
def foreach[U](f: A => U): Unit =
iterator.foreach(f)
(_: Range).view.iterator
override def iterator: Iterator[A] = new Elements(0, length)
protected class Elements(start: Int, end: Int) extends BufferedIterator[A] with Serializable {
private var i = start
def hasNext: Boolean = i < end
def next: A =
if (i < end) {
val x = self(i)
i += 1
x
} else Iterator.empty.next
def head =
if (i < end) self(i) else Iterator.empty.next
/** $super
* '''Note:''' `drop` is overridden to enable fast searching in the middle of indexed sequences.
*/
override def drop(n: Int): Iterator[A] =
if (n > 0) new Elements(i + n, end) else this
/** $super
* '''Note:''' `take` is overridden to be symmetric to `drop`.
*/
override def take(n: Int): Iterator[A] =
if (n <= 0) Iterator.empty.buffered
else if (i + n < end) new Elements(i, i + n)
else this
}
(_: Range).view.iterator.foreach
当然,这甚至不计算def foreach[U](f: A => U) { while (hasNext) f(next()) }
和filter
之间的view
:
foldLeft
答案 1 :(得分:12)
首先尝试使集合变得懒惰,所以
(1 until 1000).view.filter...
而不是
(1 until 1000).filter...
这应该避免建立中间集合的成本。使用sum
而不是foldLeft(0)(_ + _)
可能也会获得更好的性能,某些集合类型总是可能有更有效的方法来对数字求和。如果没有,它仍然更清洁,更具说服力......
答案 2 :(得分:3)
查看代码,看起来filter
确实构建了一个调用foldLeft
的新Seq。第二个跳过那一点。它不是那么多的内存,虽然它不能不帮助,但过滤的集合根本就没有构建。所有这些工作都没有完成。
范围使用TranversableLike.filter
,如下所示:
def filter(p: A => Boolean): Repr = {
val b = newBuilder
for (x <- this)
if (p(x)) b += x
b.result
}
我认为第4行的+=
就是差异。在foldLeft
中过滤会消除它。
答案 3 :(得分:1)
filter
创建一个全新的序列,然后调用foldLeft
。尝试:
(1 until 1000).view.filter(i => (i % 3 == 0 || i % 5 == 0)).reduceLeft(_+_)
这将阻止所述效果,仅仅包裹原始物品。与foldLeft
交换reduceLeft
只是装饰性的(在这种情况下)。
答案 4 :(得分:1)
现在面临的挑战是,您能想到一种更有效的方式吗?在这种情况下,并不是说你的解决方案太慢了,但它的扩展程度如何?如果不是1000,那就是10亿?有一种解决方案可以像前者一样快速地计算后一种情况。