我想要一个无限的非严格序列 x1, x2, x3...,我可以一次处理一个元素,不将结果存储在内存中以保持内存使用量恒定。为了具体说明,让我们假设它是整数序列(例如自然数、奇数、质数),尽管这个问题可能适用于更一般的数据类型。
处理无限列表的最简单方法是使用 Scala 的
我意识到这个问题相当抽象。以下是一些额外的上下文,将其与特定问题联系起来。
我在实现Meissa O'Niell的论文“The Genuine Sieve of Eratosthenes”中描述的素数生成器时遇到了这个问题,如果不涉及该论文的许多细节,很难给出一个简单的例子来说明为什么
这里有一个简化的迭代器实现,不能编译,但可以让你了解我的想法。
我需要构建一个无限数字序列的
此时,看起来我正在深入集合类扩展,这很棘手,所以我想确保只有在绝对必要的情况下才需要这样做。
处理无限列表的最简单方法是使用 Scala 的
Stream
对象。一个常见的习惯用法是编写一个返回 Stream
的函数,使用 #::
操作符向序列添加一个项,然后递归调用自身。例如,以下代码基于初始值和继承函数生成无限流的整数。 def infiniteList(n: Int, f: Int => Int): Stream[Int] = {
n #:: infiniteList(f(n), f)
}
infiniteList(2, _*2+3).take(10) print
// returns 2, 7, 17, 37, 77, 157, 317, 637, 1277, 2557, empty
我意识到上面的代码等同于库调用Stream.iterate(2)(_*2+3)
。我在这里写它只是作为这种无限Stream
习惯用法的示例。
然而,流会记忆它们的结果,使得它们的内存需求非常大且不是恒定的。文档说明如果你不保留Stream
的头部,则避免了记忆化,但实际上这可能很棘手。我可能会实现无限列表代码,在其中我认为我没有保留任何流头部,但如果它仍然具有无界内存需求,我必须弄清楚问题是我以某种方式处理我的流导致了记忆化,还是其他原因。这可能是一项困难的调试任务,并且具有代码气味,因为我试图欺骗一个明确记忆化的数据结构返回一个非记忆化的结果。
是否有人有良好的Scala示例代码,用于实现非严格、不可变、非记忆化的无限列表?
更普遍地说,我理解可遍历、可迭代、序列、流和视图的语义,但是我发现这个问题如此令人烦恼,让我感到自己可能误解了什么。在我看来,非严格性和非记忆化完全是正交的属性,但Scala似乎已经做出了决策,在Stream
中将它们合并起来,并没有简单的方法来分离它们。这是Scala的疏忽,还是我忽略了非严格性和非记忆化之间的某种深刻联系?
我意识到这个问题相当抽象。以下是一些额外的上下文,将其与特定问题联系起来。
我在实现Meissa O'Niell的论文“The Genuine Sieve of Eratosthenes”中描述的素数生成器时遇到了这个问题,如果不涉及该论文的许多细节,很难给出一个简单的例子来说明为什么
Iterator
对象是不足够的。这里有一个使用streams的完整实现,非常优雅,但内存消耗过大。这里有一个简化的迭代器实现,不能编译,但可以让你了解我的想法。
import scala.collection.mutable
object ONeillSieve {
class NumericSeries extends BufferedIterator[Int] with Ordered[NumericSeries] {
def hasNext = true
def compare(that: NumericSeries) = that.head.compare(head)
override def toString() = head + "..."
var head = 3
def next() = {
val r = head
head += 2
r
}
}
def main(args: Array[String]) {
val q = mutable.PriorityQueue[NumericSeries]()
val odds = new NumericSeries
q += odds.map(odds.head * _)
odds.next()
q += odds.map(odds.head * _)
println("Sieve = %s\nInput = %s".format(q, odds))
}
}
我需要构建一个无限数字序列的
PriorityQueue
,以最小元素为键。因此,我使用BufferedIterator
而不是普通的Iterator
。还要注意,这里无限序列的基础是奇数,但最普遍的解决方案涉及更复杂的序列。在主函数结束时,我希望队列包含两个无限序列:
- 3x(从3开始的奇数) (即9,12,15...)
- 5x(从5开始的奇数) (即25,30,35...)
odds.map(...)
返回一个Iterator
,而不是NumericSeries
,因此无法添加到优先级队列中。此时,看起来我正在深入集合类扩展,这很棘手,所以我想确保只有在绝对必要的情况下才需要这样做。
Iterator
的局限性?我通常使用Iterator
,但你可能需要一些技巧才能让它在所有你想要的情况下正常工作。因此,如果我们能看到一个天真方法失败的例子,也许我们可以提供帮助。 - Rex Kerrmap
和tail
操作,最终迫使我编写样板代码,如复制构造函数。我有一个非常干净的源代码版本Stream
,但它会耗尽内存。 - W.P. McNeillmap(_ * x)
将该系列转换为具有不同缩放因子的多个其他系列,这些系列都可以独立地推进。 - W.P. McNeill