Scala:生成固定长度序列的惯用方法(折叠无限流?) 让我们考虑生成一个随机数序列的问题,约束最终序列应该有一个固定长度 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)

Scala:生成固定长度序列的惯用方法(折叠无限流?) 让我们考虑生成一个随机数序列的问题,约束最终序列应该有一个固定长度 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),scala,stream,folding,Scala,Stream,Folding,不幸的是,这不起作用,因为foldLeft试图消耗整个无限流,导致一个无止境的循环。直观地说,根据我的预期,这种行为仅适用于使用foldRight的解决方案?也许我只是错过了另一个惯用的解决方案?我还没有检查它,但我希望您能理解: @annotation.tailrec def rndDistinctItems(n: Int, xs: List[Int] = Nil): List[Int] = if (n > 0) { val next = Random.nextInt(10)

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

我还没有检查它,但我希望您能理解:

@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

您可以使用压缩流本身的技巧:

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,3,…]
。压缩它将导致
[(1,2)、(2,2)、(2,2)、(2,3)、(3,…)]
,过滤产生
[(1,2)、(2,3)、(3,…)]
,因此最终结果是
[1,2,3,…]

我们甚至可以证明:配对后,序列具有以下属性:
a(i)。\u2=a(i+1)。\u1
。此属性将保留在筛选步骤中。过滤步骤还确保
a(i)。\u 1!=a(i).\u 2
。综合起来,我们得到了
a(i)。\u 1!=a(i)。2=a(i+1)。1实际上是
a(i)。1!=a(i+1)。\u 1


使用
fold
的方法的问题在于,在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);
}

在这里,我们首先发射头部,然后将其余的计算延迟到延迟计算的尾部。

虽然用头部压缩流是一个非常好的技巧,但我更喜欢滑动操作符:

val s = Stream.continually { Random.nextInt(10) } sliding(2) collect { case Stream(a,b) if a!=b => a } take 100 
注意:您从中得到的是迭代器,而不是流。流存储其结果(因此可多次读取)。迭代器可能只可执行一次。

那么,这样如何:

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 
       }

前面/后面的元素应该是不同的,所以所有序列都不应该重复,或者只对相邻元素重复?只有相邻元素应该是不同的。唯一元素的问题显然需要一种完全不同的方法,甚至对于任意
n
/随机数域都无法解决。很抱歉不精确…非常感谢,尾部递归是一个非常好的选择!可能应该是
xs:List[Int]=Nil
xs!=零&…
。这是一个好把戏;感谢您添加基于流的解决方案!等等,这真的有用吗?因为过滤掉一对会与前面的邻居产生新的冲突?好的,应该可以:)。毕竟,前面的邻居总是相同的,即使我们有一个较长的相同值序列。我想我今天只是有点困惑…这很聪明。我花了几分钟的时间才相信这些数字仍然是随机的。我想这个特殊的解决方案并不适用于其他情况。