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