Переписать симуляцию коллекции в Scala функциональным способом - PullRequest
0 голосов
/ 16 декабря 2018

Возьмите очень простое моделирование, в котором мы моделируем выживание или смерть человека за год - на основе сгенерированного случайного числа мы решаем, выживет ли человек год или нет:

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 функциональным способом?

Ответы [ 2 ]

0 голосов
/ 16 декабря 2018

Если вы хотите использовать функциональный способ - используйте неизменные структуры данных.Используйте метод copy для case-классов.Вы создаете другой класс case, но вы можете создать неявный метод.

Вот мы:

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()
0 голосов
/ 16 декабря 2018

Это не совсем та же сигнатура, что и Simulate, но вы можете сделать это довольно элегантно с помощью метода iterate, который вы найдете в List, Stream и т. Д.:

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

Это говорит: «начните с entry, вызовите .Simulate(0.95) для него, затем вызовите .Simulate(0.95) для полученного результата, а затем для этого результата, 100 раз подряд, собирая результаты в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)

Обратите внимание, что ни одно из этих решений не является чисто функциональным, поскольку вы полагаетесьна генераторе случайных чисел, и вы будете получать разные результаты при каждом запуске программы, но это функционально в том смысле, что в нем не используется изменяемая коллекция для сбора результатов.

...