【问题标题】:Performance around functional programming in scalascala中函数式编程的性能
【发布时间】:2021-08-23 00:34:00
【问题描述】:

我正在使用以下东西作为学习函数式编程和 scala 的一种方式,我来自 python 背景。

case class Point(x: Int, y:Int)
object Operation extends Enumeration {
  type Operation = Value
  val TurnOn, TurnOff, Toggle = Value
}

object Status extends Enumeration {
  type Status = Value
  val On, Off = Value
}

val inputs: List[String]
def parseInputs(s: String): (Point, Point, Operation)

想法是我们有一个光矩阵(Point),每个Point 可以是OnOff,如Status 中所述。 我的输入是一系列命令,询问TurnOnTurnOffToggle 从一个Point 到另一个Point 的所有灯(使用两个点定义的矩形区域是左下角和上角-右上角)。

我原来的解决方案是这样的:

type LightStatus = mutable.Map[Point, Status]
val lightStatus = mutable.Map[Point, Status]()

def updateStatus(p1: Point, p2: Point, op: Operation): Unit = {
  (p1, p2) match {
    case (Point(x1, y1), Point(x2, y2)) =>
      for (x <- x1 to x2)
        for (y <- y1 to y2) {
          val p = Point(x, y)
          val currentStatus = lightStatus.getOrElse(p, Off)
          (op, currentStatus) match {
            case (TurnOn, _) => lightStatus.update(p, On)
            case (TurnOff, _) => lightStatus.update(p, Off)
            case (Toggle, On) => lightStatus.update(p, Off)
            case (Toggle, Off) => lightStatus.update(p, On)
          }
        }
  }
}

for ((p1, p2, op) <- inputs.map(parseInputs)) {
  updateStatus(p1, p2, op)
}

现在我有lightStatus 作为地图来描述整个矩阵的结束状态。这可行,但对我来说似乎功能较少,因为我使用的是可变 Map 而不是不可变对象,所以我尝试将其重新考虑为更实用的方式,我最终得到了这个:

inputs.flatMap(s => parseInputs(s) match {
  case (Point(x1, y1), Point(x2, y2), op) =>
    for (x <- x1 to x2;
         y <- y1 to y2)
    yield (Point(x, y), op)
}).foldLeft(Map[Point, Status]())((m, item) => {
  item match {
    case (p, op) =>
      val currentStatus = m.getOrElse(p, Off)
      (op, currentStatus) match {
        case (TurnOn, _) => m.updated(p, On)
        case (TurnOff, _) => m.updated(p, Off)
        case (Toggle, On) => m.updated(p, Off)
        case (Toggle, Off) => m.updated(p, On)
      }
  }
})

我有几个关于这个过程的问题:

  1. 在我看来,我的第二个版本不像第一个版本那样简洁明了,我不确定这是因为我对函数式编程不太熟悉,还是我只是编写了糟糕的函数式代码。
  2. 有没有办法简化第二部分的语法?尤其是foldLeft 部分中的(m, item) =&gt; ??? 函数?像(m, (point, operation)) =&gt; ??? 这样的东西给了我语法错误
  3. 第二段代码的运行时间要长得多,这让我有点吃惊,因为这两个代码本质上是在做同样的事情,因为我没有太多的 Java 背景,不知道是什么导致了性能问题?

非常感谢!

【问题讨论】:

    标签: scala functional-programming immutability


    【解决方案1】:

    从函数式编程的角度来看,您的代码受到以下事实的影响...

    1. lightStatus 映射“保持状态”,因此需要突变。
    2. 大“区域”状态变化 == 大量数据更新。

    如果您可以将每个灯光状态接受为 Boolean 值,那么这里的设计不需要突变,即使在非常大的区域内也能快速更新状态。

    case class Point(x: Int, y:Int)
    
    class LightGrid private (status: Point => Boolean) {
      def apply(p: Point): Boolean = status(p)
    
      private def isWithin(p:Point, ll:Point, ur:Point) =
        ll.x <= p.x && ll.y <= p.y && p.x <= ur.x && p.y <= ur.y
    
      //each light op returns a new LightGrid
      def turnOn(lowerLeft: Point, upperRight: Point): LightGrid =
        new LightGrid(point =>
          isWithin(point, lowerLeft, upperRight) || status(point))
    
      def turnOff(lowerLeft: Point, upperRight: Point): LightGrid =
        new LightGrid(point =>
          !isWithin(point, lowerLeft, upperRight) && status(point))
    
      def toggle(lowerLeft: Point, upperRight: Point): LightGrid =
        new LightGrid(point =>
          isWithin(point, lowerLeft, upperRight) ^ status(point))
    }
    object LightGrid {  //the public constructor
      def apply(): LightGrid = new LightGrid(_ => false)
    }
    

    用法:

    val ON  = true
    val OFF = false
    val lg = LightGrid().turnOn(Point(2,2), Point(11,11)) //easy numbers
                        .turnOff(Point(8,8), Point(10,10))
                        .toggle(Point(1,1), Point(9,9))
    lg(Point(1,1))    //ON
    lg(Point(7,7))    //OFF
    lg(Point(8,8))    //ON
    lg(Point(9,9))    //ON
    lg(Point(10,10))  //OFF
    lg(Point(11,11))  //ON
    lg(Point(12,12))  //OFF
    

    【讨论】:

    • 那么你的意思是......在这种情况下,最好选择可变映射而不是严格遵循功能原则?至于你的问题,不幸的是,在后续我需要保持每盏灯的亮度,所以从技术上讲我不能做一个简单的true/false
    • 并非所有用例都非常适合函数式编程。其中之一是当您应该维护非常大的动态状态数据时。如果您遵循 FP,则必须始终重新创建新状态,与可变解决方案相比,这可能会导致更多的垃圾收集(特别是在嵌套状态模型的情况下)。
    • @sarveshseri -- 即使状态很大很复杂,如果您保留其中的大部分,新状态将与旧状态共享其大部分结构。我不认为 GC 会成为一个展示者。在非 FP 方法中,如果您要进入大型数据结构来调整它们,您将很难向自己证明所有且只有可以到达调整部分的代码片段应该看到调整后的版本.
    • @airfoyle GC 影响取决于数据的更改频率和结构。
    猜你喜欢
    • 1970-01-01
    • 2021-05-26
    • 2011-10-23
    • 1970-01-01
    • 2016-05-12
    • 2011-05-07
    • 1970-01-01
    • 1970-01-01
    • 2010-11-06
    相关资源
    最近更新 更多