【问题标题】:How to efficiently remove var in Scala如何有效地删除 Scala 中的 var
【发布时间】:2021-06-29 12:06:33
【问题描述】:

我正在尝试解决问题https://www.geeksforgeeks.org/connect-n-ropes-minimum-cost/

解决方案

def minCost(arr: Array[Int]):Int = {

  val minHeap = scala.collection.mutable.PriorityQueue.empty[Int].reverse
    
  arr.foreach{ ele =>
    minHeap += ele
  }
    
  var sum =0
    
  while(minHeap.size >1){
    
    val first = minHeap.dequeue()
    val second = minHeap.dequeue()
    
    val length = second + first//3+3 =6+9
    sum = sum + (first +second)//3+6+9
    
    minHeap.enqueue(length)
  }
    
  sum
}

我想摆脱while 循环和var。谁能提出更好的解决方案?

在下面尝试过

val res =minHeap.foldLeft(0){
  (x,y)=>
    val sum =x+y
    minHeap.enqueue(sum)
    sum
}

println(res)
res

【问题讨论】:

    标签: scala functional-programming priority-queue var


    【解决方案1】:

    如果您只想删除 varwhile 但仍然使用可变的 PriorityQueue (说实话这是一个很好的折衷方案,并且可能是在实际代码中最好的做法) 你可以只使用尾递归方法。

    type Ropes = List[Int]
    
    def connectRopes(ropes: Ropes): Int = {
      val queue = PriorityQueue.from(ropes).reverse
      
      @annotation.tailrec
      def loop(remaining: Int, acc: Int): Int = {
        if (remaining == 0) acc
        else if (remaining == 1) acc
        else {
          val rope1 = queue.dequeue()
          val rope2 = queue.dequeue()
          val newRope = rope1 + rope2
          queue.addOne(newRope)
          loop(remaining - 1, acc + newRope)
        }
      }
      
      loop(remaining = queue.size, acc = 0)
    }
    

    但是,如果您想编写一个完全不可变的解决方案只是为了习惯使用不可变的数据结构,您可以执行以下操作:

    def connectRopesFullImmutable(ropes: Ropes): Int = {
      @annotation.tailrec
      def loop(remaining: Ropes, acc: Int): Int =
        remaining match {
          case Nil =>
            acc
          
          case _ :: Nil =>
            acc
          
          case rope1 :: rope2 :: Nil =>
            rope1 + rope2 + acc
          
          case rope1 :: rope2 :: tail =>
            @annotation.tailrec
            def findTwoMin(remaining: Ropes, min1: Int, min2: Int, acc: Ropes): (Int, Int, Ropes) =
              remaining match {
                case rope :: tail =>
                  if (rope < min1) findTwoMin(remaining = tail, min1 = rope, min2 = min1, min2:: acc)
                  else if (rope < min2) findTwoMin(remaining = tail, min1, min2 = rope, min2 :: acc)
                  else findTwoMin(remaining = tail, min1, min2, rope :: acc)
                
                case Nil =>
                  (min1, min2, acc)
              }
          
            val (min1, min2, ropes) =
              if (rope1 < rope2) findTwoMin(remaining = tail, min1 = rope1, min2 = rope2, acc = List.empty)
              else findTwoMin(remaining = tail, min1 = rope2, min2 = rope1, acc = List.empty)
            val newRope = min1 + min2
            loop(remaining = newRope :: ropes, acc + newRope)
        }
      
      loop(remaining = ropes, acc = 0)
    }
    

    回答评论,问题的空间复杂度是 (AFAIK) O(1),因为该算法是尾递归函数,我们不消耗堆栈和我们只操作同一个列表,所以我们也不会消耗堆。
    时间复杂度是O(N^2),因为我们在外循环里面有一个内循环,这意味着这个算法效率很低。

    我们可能会尝试通过保持剩余绳索列表始终排序来对其进行一些优化;如下所示。应该使用 O(N log(N)),但仍然需要大量样板文件和低效率,只是因为不使用可变优先级队列。

    def connectRopesFullImmutableOptimized(ropes: Ropes): Int = {
      @annotation.tailrec
      def loop(remaining: Ropes, acc: Int): Int =
        remaining match {
          case rope1 :: rope2 :: tail =>
            val newRope = rope1 + rope2
          
            @annotation.tailrec
            def insertSorted(remaining: Ropes, acc: Ropes): Ropes =
              remaining match {
                case rope :: ropes =>
                  if (newRope > rope) insertSorted(remaining = ropes, rope :: acc)
                  else acc reverse_::: (newRope :: rope :: ropes)
                
                case Nil =>
                  (newRope :: acc).reverse
              }
          
            loop(remaining = insertSorted(remaining = tail, acc = List.empty), acc + newRope)
          
          case _ =>
            acc
        }
      
      loop(remaining = ropes.sorted, acc = 0)
    }
    

    可以看到Scastie中运行的代码

    【讨论】:

    • 我认为您还应该在代码中包含Ropes 的类型别名。
    • @LuisMiguelMejíaSuárez 你能帮忙确定算法的时间和空间复杂度吗,因为我们使用尾递归,时间复杂度为 O(1),时间复杂度为 O(nlogn),我的理解是正确的跨度>
    • @LuisMiguelMejíaSuárez 很抱歉混淆了,我问的是第一个关于使用优先队列的问题
    • @coder25 啊,是的,那个是空间中的 O(1),不确定时间复杂度,因为我不知道 PriorityQueue 操作的复杂性。
    【解决方案2】:

    我会选择unfold()。 (Scala 2.13.x)

    import scala.collection.mutable.PriorityQueue
    
    def minCost(arr: Array[Int]): Int =
      List.unfold(PriorityQueue(arr:_*).reverse){ pq =>
        Option.when(pq.size > 1) {
          val link = pq.dequeue() + pq.dequeue()
          pq.enqueue(link)
          (link, pq)
        }
      }.sum
    

    【讨论】:

    • 也可以使用Iterator.unfold来避免中间收集。
    【解决方案3】:

    非常相似的@Luis 解决方案,如果你对可变性很好,带有累加器的尾递归就足以摆脱vars 和循环。

    object ConnectRopes extends App {
      import scala.annotation.tailrec
      import scala.collection.mutable
    
      val arr = List(4, 3, 2, 6)
    
      val minHeap = mutable.PriorityQueue.from(arr)(Ordering[Int].reverse)
    
      @tailrec
      def minCost(acc: Int = 0): Int =
        if (minHeap.size > 1) {
          val connect = minHeap.dequeue + minHeap.dequeue
          minHeap.enqueue(connect)
      
          minCost(acc + connect)
        } else acc
    
      println(minCost())
    }
    

    但是,我认为仅使用列表就无法获得具有相当时间复杂度的不可变解决方案。对于不可变的优先级队列,您将需要像 this one from ScalaZ 这样的手指树。

    【讨论】:

      猜你喜欢
      • 2021-11-01
      • 2019-11-05
      • 1970-01-01
      • 2014-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多