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)
不幸的是,这不起作用,因为foldLeft试图消耗整个无限流,导致一个无止境的循环。直观地说,根据我的预期,这种行为仅适用于使用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)
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!=零&…
。这是一个好把戏;感谢您添加基于流的解决方案!等等,这真的有用吗?因为过滤掉一对会与前面的邻居产生新的冲突?好的,应该可以:)。毕竟,前面的邻居总是相同的,即使我们有一个较长的相同值序列。我想我今天只是有点困惑…这很聪明。我花了几分钟的时间才相信这些数字仍然是随机的。我想这个特殊的解决方案并不适用于其他情况。