Scala 如何在递归函数中优雅地使用纯随机数生成器?
在我用Scala制作的游戏地下城生成器中,我有一个递归函数,可以生成树状结构。它接收一个纯随机数生成器RNG,并输出一个随机树和一个新的随机数生成器 我的问题是,因为它是递归的,每次我的函数分支时,我不想将同一个RNG传递给两个分支,所以我必须在外部函数中保留一个内部变量 我的代码摘录:Scala 如何在递归函数中优雅地使用纯随机数生成器?,scala,functional-programming,Scala,Functional Programming,在我用Scala制作的游戏地下城生成器中,我有一个递归函数,可以生成树状结构。它接收一个纯随机数生成器RNG,并输出一个随机树和一个新的随机数生成器 我的问题是,因为它是递归的,每次我的函数分支时,我不想将同一个RNG传递给两个分支,所以我必须在外部函数中保留一个内部变量 我的代码摘录: def generate(parameters: RandomBSPTreeParameters)(rng: RNG): (BSPTree, RNG) = { var varRng: RNG = r
def generate(parameters: RandomBSPTreeParameters)(rng: RNG): (BSPTree, RNG) = {
var varRng: RNG = rng
def inner(size: Size, verticalSplit: Boolean): BSPTree = {
def verticalBranch = {
val leeway = size.height - parameters.minLeafEdgeLength.value * 2
val (topHeightOffset, newRng) = RNG.nextPositiveInt(leeway)(varRng)
varRng = newRng
val topHeight = parameters.minLeafEdgeLength.value + topHeightOffset
VerticalBranch(
inner(Size(size.width, topHeight), verticalSplit = false),
inner(Size(size.width, size.height - topHeight), verticalSplit = false)
)
}
def horizontalBranch = {
val leeway = size.width - parameters.minLeafEdgeLength.value * 2
val (topWidthOffset, newRng) = RNG.nextPositiveInt(leeway)(varRng)
varRng = newRng
val leftWidth = parameters.minLeafEdgeLength.value + topWidthOffset
HorizontalBranch(
inner(Size(leftWidth, size.height), verticalSplit = true),
inner(Size(size.width - leftWidth, size.height), verticalSplit = true)
)
}
def randomOrientationBranch = {
val (splitVertically, newRng) = RNG.nextBoolean(varRng)
varRng = newRng
if (splitVertically)
verticalBranch
else
horizontalBranch
}
if(size.surface > parameters.minLeafSurface)
size.shape match {
case Square if size.width > parameters.minLeafEdgeLength.value * 2 => randomOrientationBranch
case SkewedHorizontally if size.width > parameters.minLeafEdgeLength.value * 2 => horizontalBranch
case SkewedVertically if size.height > parameters.minLeafEdgeLength.value * 2 => verticalBranch
}
else Leaf(size)
}
val (firstSplitIsVertical, newRng) = RNG.nextBoolean(varRng)
varRng = newRng
val tree = inner(parameters.size, firstSplitIsVertical)
(tree, varRng)
}
有谁能指导我正确的方向,让我不必在这个函数中保留var varRng:RNG,并使它的状态更少。首先,即使你去掉了var,你的函数仍然会有副作用。原因是nextPositiveInt产生随机数的副作用。即使nextPositiveInt从不可变的预生成集合中获取数字,它也不必在每次调用时递增内部位置计数器 其次,如果数字是纯随机的,这是不可能的,依我看,但假设它们至少是独立且相同分布的i.i.d——将不同的生成器传递给不同的分支没有任何意义,就像传递同一个生成器一样——分支之间的随机数据仍然不会相关。因此,您可以在任何地方使用相同的rng 如果不是这种情况,并且我们讨论的是伪随机性,它可能会与每种情况下的不同种子自动关联,只需将rng作为内部函数的参数传递,如:
def verticalBranch(vrng: RNG) = ...
我还注意到,您并不是真的通过左或右分支传播rng-您有两个VAR,然后是lrng,rrng,这意味着您根本不需要VAR-它们可以是VAL:
如何更好地封装副作用
您可以将lRng、rRng和其他必要的生成器表示为迭代器:
val lRngGen = Iterator.continually(RNG.nextPositiveInt(rRng))
并使用类似于:
case class Accumulator(size: Size, tree: BSPTree)
val vert = (lRngGen zip rRngGen).foldLeft(Accumulator(parameters.size, emptyTree)(createNode)`
def createNode(rng: (Int, Int), acc: Accumulator): Accumulator = {
acc.size.shape match ...
}
然而,它需要用foldLeft而不是显式递归来重新思考/重写代码
case class Accumulator(size: Size, tree: BSPTree)
val vert = (lRngGen zip rRngGen).foldLeft(Accumulator(parameters.size, emptyTree)(createNode)`
def createNode(rng: (Int, Int), acc: Accumulator): Accumulator = {
acc.size.shape match ...
}