Scala中的非严格、不变、非记忆无限级数
我想要一个无限非严格级数x1,x2,x3。。。我可以一次处理一个元素,而不是为了保持内存使用恒定而记忆结果。为了明确起见,假设它是一系列整数(例如自然数、奇数、素数),尽管这个问题可能适用于更一般的数据类型 处理无限列表的最简单方法是使用Scala的Scala中的非严格、不变、非记忆无限级数,scala,stream,immutability,infinite,strict,Scala,Stream,Immutability,Infinite,Strict,我想要一个无限非严格级数x1,x2,x3。。。我可以一次处理一个元素,而不是为了保持内存使用恒定而记忆结果。为了明确起见,假设它是一系列整数(例如自然数、奇数、素数),尽管这个问题可能适用于更一般的数据类型 处理无限列表的最简单方法是使用Scala的流对象。一个常见的习惯用法是编写一个函数,返回一个流,使用:操作符向序列中添加一个术语,然后递归调用自身。例如,给定起始值和后续函数,下面生成一个无限的整数流 def infiniteList(n: Int, f: Int => Int):
流
对象。一个常见的习惯用法是编写一个函数,返回一个流
,使用:
操作符向序列中添加一个术语,然后递归调用自身。例如,给定起始值和后续函数,下面生成一个无限的整数流
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
expect语义的东西,无需记忆。Scala中似乎不存在这样的对象。我一直在尝试使用迭代器来实现无限数值序列,但是迭代器的易变性使得当您开始想要对其执行理解操作时,这变得很棘手。我也尝试从头开始编写自己的代码,但不清楚应该从哪里开始(我是否将Traversable
?)子类化,或者如何避免重新实现map
,fold
等中的功能
是否有人有很好的示例Scala代码实现了一个非严格、不可变、非记忆的无限列表
更一般地说,我理解这个问题,但我发现这个问题如此令人烦恼,这让我觉得我误解了什么。在我看来,非严格性和非记忆性是完全正交的属性,但Scala似乎已经做出了一个设计决策,将它们统一到流中,并且没有给出简单的方法将它们分开。这是Scala的疏忽,还是我忽略了非严格性和非记忆性之间的某种深层联系
我意识到这个问题相当抽象。这里有一些附加的上下文将其与特定问题联系起来
我在实现Meissa O'Niell的论文“”中描述的素数生成器的过程中遇到了这个问题,如果不从那篇论文中吸取大量细节,很难给出一个简单的例子来说明迭代器对象不充分。这是一个完整的实现,它使用的是非常优雅的,但内存消耗却非常大
这里是一个使用迭代器的简化实现,它不编译,但让您了解我想要什么
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))
}
}
我需要构建一个优先级队列
,由其最小元素键控的无限数字序列。(因此,我使用了缓冲编辑器而不仅仅是普通的迭代器)还请注意,这里无限级数的基础是奇数整数,但最普遍的解决方案涉及更复杂的级数。在main函数的末尾,我希望队列包含两个无限系列:
3x(赔率从3开始)(即9,12,15…)
5倍(赔率从5开始)(即25,30,35…)
由于赔率.map(…)
返回的是迭代器
,而不是数值序列
,因此无法将其添加到优先级队列中,因此无法编译上述内容
在这一点上,我似乎正在涉入集合类扩展,这很棘手,因此我想确保除非绝对必要,否则我不必这样做。如果您只需要能够将列表递归几次,请尝试使用Unit=>Iterator[A]
而不是原始的,尝试以下重构:
// Old way
val i = Iterator.tabulate(5)(_ + 2)
val j = i.map(_*5)
val k = i.map(_*3)
println(j.mkString(" ")) // Prints 10, 15, 20, 25, 30 as it should
println(k.mkString(" ")) // Prints nothing! (i was used up!)
// New way
val f = (u: Unit) => Iterator.tabulate(5)(_+2)
val g = f andThen (_.map(_*5))
val h = f andThen (_.map(_*3))
println(g(()).mkString(" ")) // 10, 15, 20, 25, 30
println(h(()).mkString(" ")) // 6, 9, 12, 15, 18
但从一开始,所有这些都会再次起作用。如果您需要从中间衍生出新的工作,还有一种方法可以做到这一点,只要您愿意将所有的中间元素存储在您所取得的进步之间:
val a = Iterator.tabulate(5)(_+2)
val (a1,a2) = a.duplicate
val c = a1.map(_*5)
val d = a2.map(_*3)
println(c.mkString(" ")) // 10, 15, 20, 25, 30...but stores a=2, 3, 4, 5, 6
println(d.mkString(" ")) // 6, 9, 12, 15, 18
如果这个模式和其他模式都不够好,那么您必须在集合库中创建一个类——我们称之为Generator
maybe?——这将完全满足您的需要。我希望它继承自迭代器
或Iterable
,重写或创建一个复制
方法,该方法将创建两个内部生成函数和数据处于相同状态的新副本。编辑:我将此答案保留在此处以供参考,但我发现,为了避免出现堆栈溢出,最好使用默认为惰性的集合:SeqView
->查看我的其他答案
如果您想定义一个新的集合类型,我可以这样设想:
import collection.generic.{GenericTraversableTemplate, GenericCompanion}
import collection.immutable.LinearSeq
final case class InfSeq[A](override val head: A, fun: A => A)
extends LinearSeq[A] with GenericTraversableTemplate[A, List] {
override def companion: GenericCompanion[List] = List
def apply(idx: Int): A = {
if(idx < 0) throw new IndexOutOfBoundsException(idx.toString)
var res = head
var i = idx
while(i > 0) {
res = fun(res)
i -= 1
}
res
}
def length = Int.MaxValue // ?
override def isEmpty = false
override def tail = InfSeq(fun(head), fun)
override def toString = take(4).mkString("InfSeq(", ",", ",...)")
}
显然,这还不能解决映射
、过滤器
等功能。但是,如果您小心地使用.view
,您应该可以:
val j = i.view.map(_ * 0.5)
j.take(4).foreach(println)
编辑:使用过滤器
或映射
时,下面不保留生成器
类型;实际上,尝试为生成器实现完整的“MyType”或多或少是不可能的(查看IndexedSeqView
sou
val j = i.view.map(_ * 0.5)
j.take(4).foreach(println)
import collection.immutable.StreamView
final case class Generator[A](override val head: A, fun: A => A)
extends StreamView[A, Generator[A]] {
protected def underlying = this
def length: Int = Int.MaxValue // ?
def iterator = Iterator.iterate(head)(fun)
def apply(idx: Int): A = {
if(idx < 0) throw new IndexOutOfBoundsException(idx.toString)
var res = head
var i = idx; while(i > 0) {
res = fun(res)
i -= 1
}
res
}
}
val i = Generator[Int](2, _ * 2 + 3)
i.take(4).foreach(println)
val j = i.map(_ * 0.5)
j.take(4).foreach(println)
object Generator {
def apply[A](head: A)(next: A => A): Generator[A] = {
val _head = head
new collection.IterableView[A, Nothing] {
override def head = _head
def underlying = sys.error("No underlying structure")
def iterator = Iterator.iterate(head)(next)
}
}
}
type Generator[A] = Iterable[A]
val q = collection.mutable.PriorityQueue[Generator[Int]]()
val odds = Generator(3)(_ + 2)
q += odds.map(odds.head * _)
val next = odds.tail
q += next.map(next.head * _)
q.last.take(3).mkString(",") // -> 9,12,21
q.head.take(3).mkString(",") // -> 25,35,45