繁体   English   中英

以功能方式在Scala中重写集合填充模拟

[英]Re-writing collection-populating simulation in Scala in a functional way

进行一个非常简单的模拟,我们在一年中模拟一个人的生存或死亡 - 根据生成的随机数,我们决定该人是否存活一年:

case class PersonEntry(pid: Long, year: Short, age: Short, status: Byte) {

  // return an entry where person survives and gets 1 year older
  def Mature(): PersonEntry = {
    PersonEntry(pid, (year + 1).toShort, (age + 1).toShort, status)
  }

  // return an entry where person dies (status is 0)
  def Kill(): PersonEntry = {
    PersonEntry(pid, (year + 1).toShort, (age + 1).toShort, 0)
  }

  // based on a random number decide if return Matured or Killed
  def Simulate(p: Double = 0.5): PersonEntry = {
    val rnd = scala.util.Random
    if (rnd.nextDouble < p) this.Mature() else this.Kill() 
  }

}

基于此,我们可以为同一个人获得一个新的条目,模拟1年,存活率为95%:

val per1 = PersonEntry(1, 2018, 20, 1)
val per2 = per1.Simulate(0.95)

我们接下来要做的是创建一个任意年份的模拟。 到目前为止我为此创建的是一种非常直接的方法:

import scala.collection.mutable.ListBuffer

case class PersonSimulation(entries: ListBuffer[PersonEntry]) {
 def Simulate(n: Int = 1, p: Double = 0.5): Unit = {
   for (i <- List.range(1, n)) {
     this.entries += this.entries.last.Simulate(p)
   }
 }
}

现在我们可以做到:

val per = PersonEntry(1, 2018, 20, 1)
val sim = PersonSimulation(ListBuffer(per))
sim.Simulate(100, 0.95)

// look at the result
println(sim)

PersonSimulation.Simulate方法完成了这项工作,但根本不起作用,因为它向entries ListBuffer添加了新元素。

我们怎样才能以功能的方式有效地重写PersonSimulation

它与Simulate签名不完全相同,但您可以使用您在ListStream等上找到的iterate方法相当优雅地完成此类操作:

val entry = PersonEntry(1, 2018, 20, 1)
val entries = List.iterate(entry, 100)(_.Simulate(0.95))

这是说“开始entry ,调用.Simulate(0.95)就可以了,然后调用.Simulate(0.95)你得到的,然后该结果,在连续100次,收集在一个列表的结果的结果”,看起来像这样的例子:

scala> entries.foreach(println)
PersonEntry(1,2018,20,1)
PersonEntry(1,2019,21,1)
PersonEntry(1,2020,22,1)
PersonEntry(1,2021,23,1)
PersonEntry(1,2022,24,1)
PersonEntry(1,2023,25,1)
PersonEntry(1,2024,26,1)
PersonEntry(1,2025,27,1)
PersonEntry(1,2026,28,1)
PersonEntry(1,2027,29,1)
PersonEntry(1,2028,30,1)
PersonEntry(1,2029,31,1)
PersonEntry(1,2030,32,1)
PersonEntry(1,2031,33,0)
PersonEntry(1,2032,34,0)
...

使用Stream您甚至不必提前设置迭代次数:

val entries = Stream.iterate(entry)(_.Simulate(0.95))

现在你在模拟中拥有无限的岁月,你可以看一下:

scala> entries.take(10).foreach(println)
PersonEntry(1,2018,20,1)
PersonEntry(1,2019,21,1)
PersonEntry(1,2020,22,1)
PersonEntry(1,2021,23,1)
PersonEntry(1,2022,24,1)
PersonEntry(1,2023,25,1)
PersonEntry(1,2024,26,0)
PersonEntry(1,2025,27,0)
PersonEntry(1,2026,28,0)
PersonEntry(1,2027,29,0)

请注意,这些解决方案都不是函数,因为您依赖于随机数生成器,并且每次运行程序时都会得到不同的结果,但它的功能在于它避免使用可变集合收集结果。

如果要使用功能方式 - 使用不可变数据结构。 为案例类使用方法副本。 您创建另一个案例类,但您可以创建隐式方法。

我们来了:

import scala.util.Random

case class PersonEntry(pid: Long, year: Short, age: Short, status: Byte) {

  // return an entry where person survives and gets 1 year older
  def Mature: PersonEntry = {
    copy(year = (year + 1).toShort, age = (age + 1).toShort)
  }

  // return an entry where person dies (status is 0)
  def Kill: PersonEntry = {
    copy(year = (year + 1).toShort, age = (age + 1).toShort, status = 0)
  }

  // based on a random number decide if return Matured or Killed
  def Simulate(p: Double = 0.5): PersonEntry = {
    val rnd = Random.nextDouble
    if (rnd < p) Mature else Kill
  }

}

implicit class PersonListExt(l: List[PersonEntry]) {
  def simulate(n: Int = 1, p: Double = 0.5): Map[Int, List[PersonEntry]] = {
    (1 to n).map(_ -> l.map(_.Simulate(p))).toMap
  }
}

val simulateMap: Map[Int, List[PersonEntry]]  = (1 to 10).map(i =>
  PersonEntry(i, 2018.toShort, Random.nextInt(50).toShort, 1)
).toList.simulate()

暂无
暂无

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

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