【问题标题】:Function takes two List[Int] arguments and produces a List[Int]. SCALA [duplicate]函数接受两个 List[Int] 参数并产生一个 List[Int]。斯卡拉[重复]
【发布时间】:2019-08-28 02:31:23
【问题描述】:

结果列表的元素应该在参数的元素之间交替。假设两个参数的长度相同。

使用递归

我的代码如下

val finalString = new ListBuffer[Int]
val buff2= new ListBuffer[Int]
def alternate(xs:List[Int], ys:List[Int]):List[Int] = {
    while (xs.nonEmpty) {
        finalString += xs.head + ys.head
        alternate(xs.tail,ys.tail)
    }
    return finalString.toList
}

预期结果:

alternate (List (1, 3, 5), List (2, 4, 6)) = List (1, 2, 3, 4, 6)

至于输出,我没有得到任何输出。程序仍在运行,无法执行。

有 Scala 专家吗?

【问题讨论】:

  • 可能是交替的 ( List (1 , 3, 5) , List (2 , 4, 6)) = List (1 , 2, 3, 4, 5, 6 )?

标签: scala list recursion


【解决方案1】:

条件xs.nonEmpty 始终为真,因此您有无限的while 循环。

也许您的意思是 if 而不是 while

【讨论】:

  • 是的,我现在正在学习 Scala 并从 Java 转换过来。 Scala 是一种非常棒且简单的语言,但是我对这种语言的语义感到困惑。
【解决方案2】:

您正在从方法外部访问变量,这很糟糕。我建议如下:

object Main extends App {
    val l1 = List(1, 3, 5)
    val l2 = List(2, 4, 6)

    def alternate[A](l1: List[A], l2: List[A]): List[A] = {
        if (l1.isEmpty || l2.isEmpty) List()
        else List(l1.head,l2.head) ++ alternate(l1.tail, l2.tail)
    }

    println(alternate(l1, l2))
}

仍然是递归的,但不会从方法外部访问状态。

【讨论】:

    【解决方案3】:

    假设两个列表的长度相同,您可以使用ListBuffer 来构建交替列表。 alternate 是一个纯函数:

    import scala.collection.mutable.ListBuffer
    
    object Alternate extends App {
      def alternate[T](xs: List[T], ys: List[T]): List[T] = {
        val buffer = new ListBuffer[T]
    
        for ((x, y) <- xs.zip(ys)) {
          buffer += x
          buffer += y
        }
    
        buffer.toList
      }
    
      alternate(List(1, 3, 5), List(2, 4, 6)).foreach(println)
    }
    

    【讨论】:

      【解决方案4】:

      更类似于 Scala 的方法类似于:

      def alternate(xs: List[Int], ys: List[Int]): List[Int] = {
        xs.zip(ys).flatMap{case (x, y) => List(x, y)}
      }
      
      alternate(List(1,3,5), List(2,4,6))
      // List(1, 2, 3, 4, 5, 6)
      

      【讨论】:

        【解决方案5】:

        使用match的递归解决方案

        def alternate[T](a: List[T], b: List[T]): List[T] =
          (a, b) match {
            case (h1::t1, h2::t2) =>
              h1 +: h2 +: alternate(t1, t2)
            case _ =>
              a ++ b
          }
        

        以清晰度为代价可能会更有效。


        更新

        这是更有效的解决方案:

        def alternate[T](a: List[T], b: List[T]): List[T] = {
          @annotation.tailrec
          def loop(a: List[T], b: List[T], res: List[T]): List[T] =
            (a, b) match {
              case (h1 :: t1, h2 :: t2) =>
                loop(t1, t2, h2 +: h1 +: res)
              case _ =>
                a ++ b ++ res.reverse
            }
        
          loop(a, b, Nil)
        }
        

        这保留了原始函数签名,但使用了一个内部函数,该函数是算法的高效尾递归实现。

        【讨论】:

          【解决方案6】:

          到目前为止建议的递归解决方案存在一些问题(包括您的解决方案,如果您将 while 替换为 if,它实际上会起作用):附加到列表末尾是一个线性操作,使整个事情二次的(也采用列表的.length,以及按索引访问元素),不要那样做;此外,如果列表很长,递归可能需要大量的堆栈空间,您应该尽可能使用尾递归。

          这是一个没有这两个问题的解决方案:它向后构建输出,通过 preending 元素到列表(恒定时间操作)而不是附加它们,并在结尾。它也是尾递归的:递归调用是函数中的最后一个操作,它允许编译器将其转换为循环,这样无论列表的大小如何,它都只会使用单个堆栈帧来执行。

            @tailrec
            def alternate(
              a: List[Int], 
              b: List[Int], 
              result: List[Int] = Nil
            ): List[Int] = (a,b) match {
              case (Nil, _) | (_, Nil) => result.reversed
              case (ah :: at, bh :: bt) => alternate(at, bt, bh :: ah :: result)
            }
          

          (如果列表的长度不同,那么当最短的一个结束时,整个事情就会停止,而较长的一个会被丢弃。您可能需要修改第一个case(将其分成两部分,也许)如果你想要不同的行为)。

          顺便说一句,你自己的解决方案实际上比这里建议的要好:它实际上是尾递归的(或者如果你在你的if 之后添加else 可以成为一个,现在是while),并附加到 ListBuffer 实际上并不像 List 那样糟糕。但是使用可变状态通常被认为是 scala 中的“代码气味”,应该避免(这是首先使用递归而不是循环背后的主要思想之一)。

          【讨论】:

          • 最好将递归函数放在包装函数中。这避免了主函数有不必要的参数,并使其作为方法工作。
          • 我非常同意,从长远来看,具有可变状态会使代码无法管理,但该建议有一个上下文,该状态与其他函数共享。如果状态只是函数的局部状态,则该函数可以是纯函数。事实上,Scala 库源代码中纯函数的各种实现都使用这种可变结构来提高效率。不可变的List 类本身在各种我们称为纯函数中使用ListBuffer,因为该状态是本地的。
          • @Tim 我不喜欢不必要的嵌套,没有意义。该参数的默认值确实足够 IMO。
          • @AmanAgnihotri 可变状态本身就是不好的。共享的可变状态只是(很多)更糟。是的,scala 库代码有它,是的,有时你的应用程序代码也需要它。避免它只是一个好主意,直​​到您对语言有足够的掌握,能够确定您需要它的某些情况以及为什么的确切原因。一些 java 库代码使用goto,在你的应用程序中使用它并不是一个好主意,是吗?
          • 可变状态被认为是“代码异味”,正是因为在某些代码中看到它会让读者停下来想知道它是否被全局共享,以及它是否被正确使用。最好仅出于这个原因避免它。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多