【问题标题】:Scala: Idiomatic way to generate a fixed length sequence (folding infinite stream?)Scala:生成固定长度序列的惯用方式(折叠无限流?)
【发布时间】:2013-06-28 12:32:12
【问题描述】:

让我们考虑生成随机数序列的问题,其约束条件是最终序列应该具有固定长度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 的解决方案?也许我只是缺少另一个惯用的解决方案?

【问题讨论】:

  • 前面/后面的元素应该不同所以不应该对所有序列重复还是只对邻居重复?
  • 只有邻居应该不同。唯一元素的问题显然需要一种完全不同的方法,甚至对于任意的n/随机数域都无法解决。抱歉不准确...

标签: scala stream folding


【解决方案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 
       }

【讨论】:

  • 这很聪明。花了几分钟让自己相信这些数字仍然是随机的。我想这个特殊的解决方案不太适用于其他情况。
【解决方案2】:

你可以使用这个技巧来压缩一个流:

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 的方法的问题在于您在折叠函数中自下而上构建流。这意味着为了评估流的头部,您必须评估:+ 操作的无限序列,即使头部保持不变。必须自上而下地构建适当的流 - 计算其头部并将其余部分的计算推迟到其尾部。例如:

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);
}

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

【讨论】:

  • 这是一个不错的技巧;感谢您添加基于流的解决方案!哦等等,这真的有用吗?因为过滤掉一对会与前一个邻居产生新的冲突?
  • 好的,它应该可以工作:)。毕竟前面的邻居总是相同的,即使我们有更长的相同值序列。猜猜我今天有点糊涂了……
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    我还没有检查过,但我希望你能明白:

    @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
    

    【讨论】:

    • 非常感谢,尾递归是一个很好的选择!应该是xs: List[Int] = Nilxs != Nil && ...
    猜你喜欢
    • 2019-06-11
    • 2012-03-31
    • 1970-01-01
    • 2018-01-24
    • 1970-01-01
    • 1970-01-01
    • 2016-10-16
    • 1970-01-01
    • 2020-03-11
    相关资源
    最近更新 更多