【问题标题】:Creating an iterator of subsequences from an iterator of items从项的迭代器创建子序列的迭代器
【发布时间】:2014-03-25 11:45:18
【问题描述】:

如果我有一个Seq,那么很容易生成到给定长度限制的所有子序列,如下所示:

def subseqs[A](n: Int)(s: Seq[A]) = {
  (1 to n).flatMap(s.sliding)
}                                            

subseqs(3)(List(1, 2, 3, 4)) foreach println    //> List(1)
                                                //| List(2)
                                                //| List(3)
                                                //| List(4)
                                                //| List(1, 2)
                                                //| List(2, 3)
                                                //| List(3, 4)
                                                //| List(1, 2, 3)
                                                //| List(2, 3, 4)

但是,是否有一种惯用的(并且相当有效)方法可以将迭代器作为输入,产生一个迭代器(或者可能是一个流)作为输出?

更新:我有一个实现迭代器的working implementation,它在一次传递中完成,因此需要很少的内存,但相对较长并且使用可变变量(var 和 @ 987654325@) - 如果有帮助,可以发布。我希望有一种更优雅的方式使用高阶函数...

上面的方法(使用sliding())不起作用,因为迭代器在第一遍就用完了,不能再使用了。

使用sliding()inits() 的组合更好,但会错过预期子序列的尾部:

def subseqsi[A](n: Int)(i: Iterator[A]) = {
  //(1 to n).flatMap(i.sliding) 
  // no - this exhausts the iterator

  i.sliding(n).flatMap { _.inits.filterNot(_.isEmpty) } 
  //nearly, this misses off subsequences towards the end
} 
                                              //> List(1, 2, 3)
                                              //| List(1, 2)
                                              //| List(1)
                                              //| List(2, 3, 4)
                                              //| List(2, 3)
                                              //| List(2)

我的输入数据是一个未知(可能非常大)大小的迭代器。输出子序列的顺序无关紧要。

【问题讨论】:

  • 这是我以前用 Haskell 写的一个高尔夫球。我将给出 Haskell 代码并将其翻译成 Scala 留给读者 :) init . tails <=< tail . inits 适用于有限列表和无限列表,所以它必须通过构造变得懒惰! fish/<=<运算符来自Control.Monad,是Kleisli组成。

标签: scala functional-programming iterator


【解决方案1】:

只需使用Stream

def subseqs[A](n: Int)(iter: Iterator[A]) = {
  val i = iter.toStream
  1 to n flatMap i.sliding
}

Stream 和 Iterator 一样是惰性的,但它存储所有已计算的值。

【讨论】:

  • 这很好,很优雅,并且产生了正确的输出,但急切地创建了n Streams 的 IndexedSeq,因此它在(1 to 1000000).iterator 等大输入时表现不佳。如果将toStream 添加到(1 to n) 我认为它的行为更懒惰......?
  • 1 to n 当然是严格的。 toStreamiterator 让它变得懒惰,是的。
  • 好的,toStream 有点帮助,但不幸的是,随着n 的增加,它在内存上仍然非常昂贵(大概是因为我们最终要记住整个序列?)。对于n最多1000左右,差异是很小的因素;对于 n=1,000,000,我以当前的堆大小碰到了 GC 墙。使用iterator 更好; n=1,000,000 是可以的,但另一个 10 的因素使它再次挣扎。
  • 当然,当您增加n 时,您需要更多内存。 Scala的stdlib不支持自动清理缓存的数据结构,如果n太大,你要么自己找一个库,要么自己构建。
【解决方案2】:

这是另一种使用 Streams 的方法 - 远不如 flatMap/sliding 方法(它用于最后几个元素)优雅,但速度更快,内存消耗更少。

def subseqs[A](n: Int)(s: Iterator[A]) = {
  def loop(s: Stream[A], history: Vector[A]): Stream[Seq[A]] = {
    if (s.isEmpty) (1 to n).flatMap(history.sliding).toStream
    else if (history.length == n) 
      history.inits.filterNot(_.isEmpty).toStream #::: loop(s.tail, history.tail :+ s.head)
    else loop(s.tail, history :+ s.head)
  }
  loop(s.toStream, Vector())
}

这会建立一个历史记录,直到有 n 个元素,输出 inits,然后移动历史记录。然而,不仅代码更丑,它还以丑陋的顺序返回结果,因为需要将流的结尾作为一种特殊情况来处理:

subseqs(3)(List(1, 2, 3, 4)) foreach println  > Vector(1, 2, 3)
                                              | Vector(1, 2)
                                              | Vector(1)
                                              | Vector(2)
                                              | Vector(3)
                                              | Vector(4)
                                              | Vector(2, 3)
                                              | Vector(3, 4)
                                              | Vector(2, 3, 4)

我仍然希望优雅的快速解决方案是可能的......

【讨论】:

  • 你看到我的 Haskell 解决方案了吗?
  • 我做到了,谢谢 - 它看起来很酷,但我目前不知道如何将它翻译成 Scala,或者它可能如何执行 - 显然 Haskell 和 Scala 在惰性方面有很大不同,编译等
猜你喜欢
  • 1970-01-01
  • 2016-07-24
  • 2018-05-20
  • 1970-01-01
  • 1970-01-01
  • 2010-10-09
  • 1970-01-01
  • 2017-06-12
  • 2021-10-15
相关资源
最近更新 更多