【问题标题】:How do I generate a random number using functional state?如何使用功能状态生成随机数?
【发布时间】:2015-07-24 09:13:22
【问题描述】:

我正在努力弄清楚如何将 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),以此类推。

【问题讨论】:

  • answer 使用随机掷骰子解释 Scalaz State

标签: scala random functional-programming state


【解决方案1】:

我们希望生成随机数并将随机数生成器(截至目前为 RNG)(scala.util.Random 类型)保持为您的 State 类中的状态。

我们可以将类型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,我们想在其中掷几次骰子并返回一个随机数列表。因此这个函数的类型是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 函数来大幅清理,就像他们在fpinscala answers on github 中所做的那样。

现在我们可以将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)

【讨论】:

  • 非常感谢您提供如此周到的帖子。我最大的挣扎是根本不知道如何使用runval roll = nextIntInRange(1, 6); val rng = new Random(1L); val (one, rng2) = roll.run(rng) 在向我展示如何使用它方面非常有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-25
  • 2014-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
相关资源
最近更新 更多