Scala中的非严格,不可变,非记忆无限系列

时间:2013-01-12 21:32:10

标签: scala stream immutability infinite strict

我想要一个无限的非严格系列x 1 ,x 2 ,x 3 ...我可以使用一个元素一次,不记住结果,以保持我的内存使用不变。为了特殊性,我们假设它是一系列整数(例如自然数,奇数,素数),尽管这个问题可能适用于更一般的数据类型。

使用无限列表的最简单方法是使用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的头部,那么documentation states就会避免记忆,但实际上这可能很棘手。我可以实现无限列表代码,其中我不我坚持任何流头,但如果它仍然有无限的内存要求我必须弄清楚问题是我的问题我以某种方式处理我的流,这会导致记忆,或者是否是其他东西。这可能是一个困难的调试任务,并且有代码味道,因为我试图欺骗一个明确的memoized数据结构返回一个非memoized结果。

我想要的是Stream语义而没有记忆的东西。 Scala中似乎不存在这样的对象。我一直在尝试使用迭代器来实现无限数字系列,但是当你开始想要对它们进行理解操作时,迭代器的可变性使得这很棘手。我也尝试从头开始编写自己的代码,但是我不知道应该从哪里开始(我是否继承Traversable?)或如何避免重新实现map中的功能,{{1}等等。

是否有人拥有非严格,不可变,非记忆无限列表实现的Scala代码示例?

更一般地说,我理解semantics of traversable, iterable, sequence, stream, and view,但事实上我觉得这个问题很令人烦恼,这让我觉得我误解了一些事情。在我看来,非严格性和非记忆化是完全正交的属性,但Scala似乎做出了一个设计决定,在fold中统一它们并且没有简单的方法将它们分开。这是对Scala的疏忽吗,还是我忽视的非严格和非记忆之间有一些深层的联系?


我意识到问题相当抽象。以下是一些将其与特定问题联系起来的其他背景信息。

我在Meissa O'Niell的论文“The Genuine Sieve of Eratosthenes”中描述的实现素数生成器的过程中遇到了这个问题,并且很难给出Stream对象的简单示例没有从该论文中提取很多细节,这是不够的。这是一个使用streams的完整实现,它非常优雅但内存消耗量很大。

这是一个带有迭代器的简化实现,它不会编译,但会让你知道我想要什么。

Iterator

我需要构建一个由其最小元素键入的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。)另请注意,此处无限级数的基础是奇数整数,但最通用的解决方案涉及更复杂的系列。在main函数的最后,我希望队列包含两个无限系列:

  1. 3x(赔率从3开始)(即9,12,15 ......)
  2. 5x(赔率从5开始)(即25,30,35 ......)
  3. 上述内容无法编译,因为Iterator返回odds.map(...),而不是Iterator,因此无法添加到优先级队列中。

    此时看起来我正在涉及集合类扩展,这很棘手所以我想确保除非绝对必要,否则我不必这样做。

4 个答案:

答案 0 :(得分:3)

编辑:使用Generatorfilter时,以下内容不会保留map类型;确实试图为生成器实现完整的“MyType”或多或少是不可能的(查看IndexedSeqView源代码以查看混乱)。

但是有更简单的方法(参见我的第三个答案)


好的,我的第二次尝试。为了保持mapfilter等的惰性行为,最好的方法可能是SeqViewStreamView

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)

答案 1 :(得分:1)

如果您只需要能够多次递归列表,请尝试使用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? - 这将完全符合您的要求。我将继承自IteratorIterable,覆盖或创建duplicate方法,该方法将创建两个具有内部生成函数和数据处于相同状态的新副本。

答案 2 :(得分:1)

这有望成为最直接的方法。只需创建一个懒惰的Iterable

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

答案 3 :(得分:0)

编辑:我在这里留下这个答案供参考,但我发现不要遇到堆栈溢出,最好使用默认为懒惰的集合:SeqView - &gt ;看到我的另一个答案。


如果你想定义一个新的集合类型,这就是我想象的那样:

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(", ",", ",...)")
}

实施例

val i = InfSeq[Int](2, _ * 2 + 3)
i.take(4).foreach(println)

显然,这还没有解决mapfilter等功能。但是如果你小心使用.view,你应该没问题:

val j = i.view.map(_ * 0.5)
j.take(4).foreach(println)