【问题标题】:Scala return value different than expectedScala返回值与预期不同
【发布时间】:2016-08-31 05:55:49
【问题描述】:

我正在学习 Scala 并编写一个函数(递归)来计算括号的数量:+1 表示打开,-1 表示关闭,以便匹配和平衡字符列表中的所有括号。如果括号是平衡的,它应该返回 0。

我想出了这个(用大量的打印语句来理解发生了什么):

def countPar(charList: List[Char], count: Int): Int = {

  if (count < 0) {
    println("negative count, returning", count)
    count
  }
  else if (charList.isEmpty) {
    println("empty list, returning", count)
    count
  }
  else if (charList.head.equals('(')) {
    println("head is ", charList.head, " count + 1:", count + 1)
    count + countPar(charList.tail, count + 1)
  }
  else if (charList.head.equals(')')) {
    println("head is ", charList.head, " count - 1:", count - 1)
    count + countPar(charList.tail, count - 1)
  }
  else {
    println("head is ", charList.head, " count:", count)
    countPar(charList.tail, count)
  }
}

val parCount = countPar("(a(b)c)".toList, 0)

println(parCount)

打印语句似乎证实了逻辑是有效的,但最终的返回值是错误的:

(head is ,(, count + 1:,1)
(head is ,a, count:,1)
(head is ,(, count + 1:,2)
(head is ,b, count:,2)
(head is ,), count - 1:,1)
(head is ,c, count:,1)
(head is ,), count - 1:,0)
(empty list, returning,0)
parCount: Int = 4

我错过了什么?

【问题讨论】:

  • 为什么count + 在几个递归的情况下?
  • 我正在跟踪括号的数量并将其作为参数传递。 +1 开头,-1 结尾。
  • 想想递归调用应该返回什么。你确定count + 没有重复计算吗?
  • 但现在我明白你的意思了......没有理由,这就是问题所在

标签: scala recursion


【解决方案1】:

您目前正在以非常命令式/面向对象的风格进行编码。但这需要一种功能性的递归方法:

def isBalanced(string: List[Char], count: Int=0): Boolean = {
  if (count < 0) { false }  // We can only be balanced if ( precedes )
  else {
    string match {
      case Nil => count == 0  // Is the count 0? If so, however we got here was balanced!
      case '('::tail => isBalanced(tail, count + 1)  // ( prepended to some list
      case ')'::tail => isBalanced(tail, count - 1)  // ) prepended to some list
      case _::tail => isBalanced(tail, count)
    }
  }
}

请注意,我们的外部函数用它的签名来回答这个问题:给定一个任意字符列表,该列表是否平衡? (“是或否”意味着一个布尔值:使用整数会使该函数的用户变得复杂。)与所有递归函数一样,该函数会询问问题是否可以简单地解决,如果不是,它会做一些工作然后简单地返回递归调用的结果。

为此,我们首先定义一个基本案例。也就是说,如果列表为空,我们只返回计数是否为 0。如果是,我们知道括号是平衡的。

其次,我们定义递归案例。这里我们相信isBalanced 返回一个正确的结果并且只处理增量不同。这两行处理:

case '('::tail => isBalanced(tail, count + 1)
case ')'::tail => isBalanced(tail, count - 1)

在每个中,我们相应地增加或减少我们的计数。最后一行(case _::tail)处理所有其他情况,这不应该影响最终结果。

使用 Scala 强大的大小写匹配功能 (match),这非常容易。我们可以在比赛前设置一个简单的后卫,以确保一旦平衡变为负数,我们就会提前退出。然后是针对字符串输入的模式映射问题。这比使用无穷无尽的 if-else 子句更清楚。

还要注意创建默认参数值的技巧,因此您无需传入0。这使您的界面更加清晰,同时允许您重复使用该功能。

为了证明正确性:

isBalanced("((()))".toList)  // true
isBalanced(")))(((".toList)  // false
isBalanced("(()())".toList)  // true
isBalanced("((()())".toList)  // false
isBalanced("Foo () bar ()".toList)  // true

关于打印日志行的说明:如果您需要这样做来跟踪案例映射是如何发生的,或者在案例子句中进行任意操作,您可以这样做:

def isBalanced(string: List[Char], count: Int=0): Boolean = {
  if (count < 0) { false }
  else {
    string match {
      case Nil => count == 0
      case '('::tail => {
        println("Found an open paren")
        isBalanced(tail, count + 1)
      }
      case ')'::tail => {
        println("Found a close paren")
        isBalanced(tail, count - 1)
      }
      case _::tail => isBalanced(tail, count)
    }
  }
}

【讨论】:

  • 不能用case '(' :: tail =&gt; ...吗?
  • @Clashsoft 是的!感谢您进一步简化它。
  • 另外,我认为您可以添加 @tailrec 注释,因为实现是尾递归的。
  • @Clashsoft 我考虑过...但我认为编译器无论如何都可能会接受它,并且编译器优化的注释可能比 OP 所处的位置更进一步,学习曲线.
【解决方案2】:

问题只是返回count + countPar(charList.tail, count + 1) 而不是countPar(charList.tail, count + 1)(右括号也类似)。

递归函数的要点是根据头值更新计数,并将其传递给递归调用,递归调用将根据尾值更新它(递归调用将做同样的事情,直到尾巴是空的)。这意味着递归调用将返回正确的结果:无需添加任何内容。

编辑: 我认为一旦像这样重构它也会变得更加清晰(重要的部分是带有注释的部分,否则我尽量不改变你的方法):

def countPar(charList: List[Char], count: Int): Int = {
  if (count < 0) {
    println("negative count, returning", count)
    count
  } else if (charList.isEmpty) {
    println("empty list, returning", count)
    count
  } else {
    val updatedCount =
      if (charList.head.equals('('))
        count + 1
      else if (charList.head.equals(')'))
        count - 1
      else
        count
    println(s"head is ${charList.head}, count: ${updatedCount}")
    // We see here that the reursive call is the same in the 3 cases: the
    // only difference is how we update the count
    countPar(charList.tail, updatedCount)
  }
}

【讨论】:

    猜你喜欢
    • 2014-06-18
    • 2012-01-25
    • 1970-01-01
    • 2019-12-27
    • 2015-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-22
    相关资源
    最近更新 更多