[英]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
签名不完全相同,但您可以使用您在List
, Stream
等上找到的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.