以函数式方式在 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
的签名并不完全相同,但您可以使用 iterate
方法相当优雅地完成此类操作,您可以在 List
、[=17] 上找到=],等等:
val entry = PersonEntry(1, 2018, 20, 1)
val entries = List.iterate(entry, 100)(_.Simulate(0.95))
这表示 "start with entry
, call .Simulate(0.95)
on it, then call .Simulate(0.95)
on the result you get, and then on that result, for 100 times in a row, collecting the results in a list",它看起来像这样的例子:
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)
请注意,这些解决方案都不是纯粹的功能,因为您依赖于随机数生成器,每次您都会得到不同的结果运行 程序,但它的功能在于它避免使用可变集合来收集结果。
如果你想使用函数式方式——使用不可变数据结构。
对案例 classes 使用方法复制。
您创建了另一个案例 class 但您可以创建隐式方法。
我们在这里:
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()
进行一个非常简单的模拟,我们对一个人在一年内的存活或死亡进行建模 - 根据生成的随机数,我们决定该人是否存活一年:
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
的签名并不完全相同,但您可以使用 iterate
方法相当优雅地完成此类操作,您可以在 List
、[=17] 上找到=],等等:
val entry = PersonEntry(1, 2018, 20, 1)
val entries = List.iterate(entry, 100)(_.Simulate(0.95))
这表示 "start with entry
, call .Simulate(0.95)
on it, then call .Simulate(0.95)
on the result you get, and then on that result, for 100 times in a row, collecting the results in a list",它看起来像这样的例子:
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)
请注意,这些解决方案都不是纯粹的功能,因为您依赖于随机数生成器,每次您都会得到不同的结果运行 程序,但它的功能在于它避免使用可变集合来收集结果。
如果你想使用函数式方式——使用不可变数据结构。 对案例 classes 使用方法复制。 您创建了另一个案例 class 但您可以创建隐式方法。
我们在这里:
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()