繁体   English   中英

如何使用功能状态生成随机数?

[英]How do I generate a random number using functional state?

我正在努力弄清楚如何将状态的功能表示与Scala的Random类合并以生成随机整数。 我正在学习《 Scala中的函数编程 》一书,因此大部分代码都从那里获取。

这是直接来自本书的State类的样子:

case class State[S, +A](run: S => (A, S))

这就是我想要做的:

object State {
  type Rand[A] = State[A, Random] // the Random from scala.util.Random

  def nextIntInRange(from: Int, to: Int): Rand[Int] =
  ??? _.nextInt(from - to) + from ??? // unsure about much of this

  def get(a: Rand[A]): A = ??? // also unsure; should modify state

  def getAndPreserveState(a: Rand[A]): A = ??? // ditto; should not modify state

  def diceRolls(n: Int) = {
    val roll = nextIntInRange(1, 6) 
    go(n: Int, acc: List[Int]): List[Int] = {
      if (n >= 0) go(n-1, get(roll) :: acc) else acc
    }
    go(n, List())
  }

我花了几个小时试图弄清楚如何与State的构造函数一起工作,但尚未实现我想要的目标,因此几乎完全缺乏对如何实现这前三种方法的理解。

我的目标是能够将diceRolls与任何大小的整数以及任何给定的起始种子一起使用,并生成不会改变的整数列表。 换句话说, diceRolls(3)可能是List(3,3,2) ,如果是这样,将其重写为diceRolls(7).take(3)必须再次导致List(3,3,2) ,如此上。

我们要生成随机数,并在State类中将随机数生成器(至今为RNG)(类型为scala.util.Random )保持为状态。

我们可以将类型Rand[A]定义为:

type Rand[A] = State[Random, A]

我们希望能够获得一个范围内的随机整数。 如果我们有RNG,可以使用以下命令轻松完成:

def randomInRange(rng: Random, start: Int, end: Int) = 
  rng.nextInt(end - start + 1) + start

randomInRange(new Random(1L), 10, 20) // Int = 14

但是我们想使用先前状态的RNG,因此我们在run函数中定义了具有相同代码的State

def nextIntInRange(from: Int, to: Int): Rand[Int] = 
  State((r: Random) => (r.nextInt(to - from + 1) + from, r))

我们的nextIntInRange函数返回一个随机数和RNG。 让我们定义roll来测试它:

val roll = nextIntInRange(1, 6)

val rng = new Random(1L)
val (one, rng2) = roll.run(rng)
// (Int, scala.util.Random) = (4,scala.util.Random@5fb84db9)
val (two, rng3) = roll.run(rng2)
// (Int, scala.util.Random) = (5,scala.util.Random@5fb84db9)

到目前为止,我们可能会想到,但是如果我们两次使用rng ,我们希望接收相同的随机数:

val rng = new Random(1L)
val (one, _) = roll.run(rng) // 4
val (two, _) = roll.run(rng) // 5

我们得到两个不同的数字,这不是我们使用State时想要的。 我们希望使用相同RNG的滚动返回相同结果。 问题在于Random改变其内部状态,因此我们不能将随后的状态更改放入State

Scala的函数编程中,此问题通过定义一个新的随机数生成器解决,该生成器还会在nextInt上返回它的状态。

尽管使用Random不能达到使用State的目的,但是我们可以尝试将其余功能实现为一项教育活动。

让我们看一下getgetAndPreserveState

def get(a: Rand[A]): A = ??? // also unsure; should modify state

def getAndPreserveState(a: Rand[A]): A = ??? // ditto; should not modify state

如果我们查看类型签名,则需要像roll函数一样传递Rand[A]并返回此函数的结果。 这些功能很奇怪,原因有两个:

  • 我们的roll函数需要一个Random实例才能获得结果,但是我们没有类型为Random参数。
  • 返回类型为A ,因此,如果我们有一个Random实例,则在调用a.run(ourRng)之后只能返回随机数,但是我们应该对状态做些什么。 我们想明确地保持状态。

让我们把试图diceRolls宝贵状态的这些函数留在后面,并实现最终函数diceRolls ,在该函数中我们要掷骰子几次并返回一个随机数列表。 因此,此函数的类型将为Rand[List[Int]]

我们已经有了一个函数roll ,现在需要多次使用它,我们可以使用List.fill

List.fill(10)(roll) // List[Rand[Int]] 

但是,结果类型为List[Rand[Int]]而不是Rand[List[Int]] F[G[_]]G[F[_]]是一种通常称为sequence的操作,让它直接为State

object State {

  def sequence[A, S](xs: List[State[S, A]]): State[S, List[A]] = {
    def go[S, A](list: List[State[S, A]], accState: State[S, List[A]]): State[S, List[A]] = 
      list match { 
        // we have combined all States, lets reverse the accumulated list
        case Nil => 
          State((inputState: S) => {
            val (accList, state) = accState.run(inputState)
            (accList.reverse, state)
          })
        case stateTransf :: tail => 
          go(
            tail,
            State((inputState: S) => {
              // map2
              val (accList, oldState) = accState.run(inputState) 
              val (a, nextState) = stateTransf.run(oldState)
              (a :: accList, nextState) 
            })
          )
      }
    // unit
    go(xs, State((s: S) => (List.empty[A], s)))
  }

}

关于Rand[Int]情况的一些解释:

// use the RNG in to create the previous random numbers
val (accList, oldState) = accState.run(inputState)
// generate a new random number 
val (a, nextState) = stateTransf.run(oldState)
// add the randomly generated number to the already generated random numbers
// and return the new state of the RNG
(a :: accList, nextState) 

我的执行State.sequence可以通过定义相当清理unitmap2的功能就像他们在做了GitHub上fpinscala答案

现在,我们可以将diceRolls函数定义为:

def diceRolls(n: Int) = State.sequence(List.fill(n)(roll))

我们可以将其用作:

diceRolls(5).run(new Random(1L))
// (List[Int], scala.util.Random) = (List(4, 5, 2, 4, 3),scala.util.Random@59b194af)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM