Scala中的非严格、不变、非记忆无限级数

Scala中的非严格、不变、非记忆无限级数,scala,stream,immutability,infinite,strict,Scala,Stream,Immutability,Infinite,Strict,我想要一个无限非严格级数x1,x2,x3。。。我可以一次处理一个元素,而不是为了保持内存使用恒定而记忆结果。为了明确起见,假设它是一系列整数(例如自然数、奇数、素数),尽管这个问题可能适用于更一般的数据类型 处理无限列表的最简单方法是使用Scala的流对象。一个常见的习惯用法是编写一个函数,返回一个流,使用:操作符向序列中添加一个术语,然后递归调用自身。例如,给定起始值和后续函数,下面生成一个无限的整数流 def infiniteList(n: Int, f: Int => Int):

我想要一个无限非严格级数x1,x2,x3。。。我可以一次处理一个元素,而不是为了保持内存使用恒定而记忆结果。为了明确起见,假设它是一系列整数(例如自然数、奇数、素数),尽管这个问题可能适用于更一般的数据类型

处理无限列表的最简单方法是使用Scala的
对象。一个常见的习惯用法是编写一个函数,返回一个
,使用
操作符向序列中添加一个术语,然后递归调用自身。例如,给定起始值和后续函数,下面生成一个无限的整数流

  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