Scala:生成固定长度序列的惯用法(折叠无限流?)

时间:2013-07-01 11:38:06

标签: scala stream folding

让我们考虑生成随机数序列的问题,其约束条件是最终序列应该具有固定长度n并且前面/后面的元素应该是不同的(即邻居应该是不同的)。我的第一个惯用方法是:

val seq = Stream.continually{ Random.nextInt(10) }
                .foldLeft(Stream[Int]()){ (all: Stream[Int], next: Int) =>
                  if (all.length > 0 && all.last != next)
                    all :+ next
                  else
                    all
                }
                .take(n)

不幸的是,这不起作用,因为foldLeft尝试消耗整个无限流,导致无限循环。直观地并且根据this question我会期望这种行为仅适​​用于使用foldRight的解决方案?也许我只是错过了另一种惯用的解决方案?

4 个答案:

答案 0 :(得分:4)

您可以通过自己压缩流来使用该技巧:

def randSeq(n: Int): Stream[Int] = {
  // an infinite stream of random numbers
  val s = Stream.continually{ Random.nextInt(10) }
  s.zip(s.tail) // pair each number with it sucessor
   .filter((pair) => pair._1 != pair._2) // filter out equal pairs
   .map(_._1)   // break pairs again
   .take(n);    // take first n
}

然后你可以过滤掉连续的相等元素,最后得到所需的数量。

更新:是的,它会起作用。假设你有[1,2,2,2,3,...]。压缩它将导致[(1,2),(2,2),(2,2),(2,3),(3,..),...],过滤产生[(1,2),(2,3),(3,..),...],因此最终结果为[1,2,3,...]

我们甚至可以证明:配对后,序列具有以下属性:a(i)._2 = a(i+1)._1。此属性在过滤步骤中保留。过滤步骤还确保a(i)._1 != a(i)._2。放在一起我们确实a(i)._1 != a(i)._2 = a(i+1)._1 a(i)._1 != a(i+1)._1


使用fold的方法存在的问题是,您在折叠函数中自下而上构建了Stream。这意味着,为了评估流的头部,您必须评估无限序列的:+操作,即使头部保持不变。必须从上到下构建适当的流 - 计算其头部并推迟其尾部的其余部分的计算。例如:

def randSeq1(n: Int): Stream[Int] = {
  def g(s: Stream[Int]): Stream[Int] =
    s match {
      case h #:: t => h #:: g(t.dropWhile(_ == h))
    }
  g(Stream.continually{ Random.nextInt(10) }).take(n);
}

这里我们首先发出头部,然后将剩余的计算推迟到懒惰评估的尾部。

答案 1 :(得分:1)

我还没有检查过,但我希望你能得到这个想法:

@annotation.tailrec 
def rndDistinctItems(n: Int, xs: List[Int] = Nil): List[Int] = if (n > 0) {
    val next = Random.nextInt(10)
    val shouldTryAgain = xs != Nil && next == xs.head
    if (shouldTryAgain) rndDistinctItems(n, xs)
    else rndDistinctItems(n - 1, next::xs)
} else xs

答案 2 :(得分:1)

虽然使用自己的头部压缩流是一个非常好的技巧,但我更喜欢sliding运算符:

val s = Stream.continually { Random.nextInt(10) } sliding(2) collect { case Stream(a,b) if a!=b => a } take 100 

注意:你得到一个Iterator,而不是Stream。 Stream会记住其结果(因此可以多次迭代)。迭代器可能只能迭代一次。

答案 3 :(得分:1)

那么,这个怎么样:

scala> val M = 10
M: Int = 10

scala> val seq = Stream.iterate(Random.nextInt(M)){ x => 
         val nxt = Random.nextInt(M-1); if(nxt >= x) nxt + 1 else nxt 
       }