【问题标题】:Checking whether an Array[T] is sorted without local tailrec?检查 Array[T] 是否在没有本地 tailrec 的情况下排序?
【发布时间】:2018-09-21 08:47:46
【问题描述】:

实现isSorted,检查一个Array[A]是否按照给定的比较函数排序:

def isSorted[A](as: Array[A], ordered: (A,A) => Boolean): Boolean

这是我的实现

@tailrec
def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = {
  if(as.length==0 || as.length == 1 || as.isEmpty) true
  else if(!ordered(as(0),as(1))) false
  isSorted(as.tail,ordered)
}

我遇到了这个异常:
java.lang.UnsupportedOperationException: empty.tail

不太明白,as 为空时应该返回 true。

【问题讨论】:

  • 像这样使用 return 作为布尔值。 if(!ordered(as(0),as(1))) 返回 false

标签: scala


【解决方案1】:

在 Scala 中,在方法或块中计算的最后一个表达式成为该方法或块的值。

在您的情况下,在方法内评估的最后一个表达式是:

isSorted(as.tail,ordered)

所以,这是返回值。 总是

你的方法中在这个表达式之前还有另一个表达式:

if(as.length==0 || as.length == 1 || as.isEmpty) true
else if(!ordered(as(0),as(1))) false

但是:

  • 这个表达式没有副作用
  • 此表达式的值未存储在任何地方
  • 不返回这个表达式的值

因此,这个表达式本质上是一个空操作,而你的方法实际上就是这样:

@tailrec
def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = 
  isSorted(as.tail,ordered)

您的方法将简单地递归直到数组为空,然后抛出异常,因为您正尝试使用空数组的尾部再次递归。

最简单的解决方法是让最后一个表达式成为更大表达式的一部分,这样你的方法就只包含一个表达式:

@tailrec
def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = {
  if(as.length==0 || as.length == 1 || as.isEmpty) true
  else if(!ordered(as(0),as(1))) false
  else isSorted(as.tail,ordered)
//↑↑↑↑ This is the only change needed.
}

现在,让我们进行一次小旅行:Scala 风格!

您的空格样式不一致。有时,运算符周围有空格,有时没有,不清楚何时选择一个或另一个,以及它的含义是什么。例如,这里:

if(as.length==0 || as.length == 1 || as.isEmpty) true
//          ↑↑ ↑↑↑↑         ↑↑↑↑ ↑↑↑↑

您使用什么标准来决定何时使用空格?你在|| 和第二个== 周围使用空格但不是第一个是什么意思?你想告诉我,你的代码的读者,关于这个决定的重要信息是什么?

就我个人而言,我会这样写:

if(as.length == 0 || as.length == 1 || as.isEmpty) true
//          ↑↑↑↑ ↑↑↑↑         ↑↑↑↑ ↑↑↑↑

这也符合标准的 Scala 社区风格指南。

同样,您在参数列表中的逗号后使用空格,而在参数列表中不使用空格。标准 Scala 社区风格指南建议在逗号后使用空格以提高可读性:

else if(!ordered(as(0), as(1))) false
//                     ↑
else isSorted(as.tail, ordered)
//                    ↑

标准的 Scala 社区风格指南还建议在 ifwhile 等控制流关键字之后使用空格,以清楚地将它们与方法调用区分开来:

if (as.length == 0 || as.length == 1 || as.isEmpty) true
//↑
else if (!ordered(as(0), as(1))) false
//     ↑

另外,请注意,检查零长度和空虚是多余的,它们是同一回事:

if (as.length == 1 || as.isEmpty) true

最后,既然我们的方法只包含一个表达式,我们就不再需要花括号了:

@tailrec
def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = 
  if (as.length == 1 || as.isEmpty) true
  else if (!ordered(as(0), as(1))) false
  else isSorted(as.tail, ordered)

然而,实际上有一个更好的方法来解决这个问题:如果每个连续的元素对都被排序,则对数组进行排序:

def isSorted[A](as: Array[A], ordered: (A, A) => Boolean) = 
  as.sliding(2).forall { case Array(a, b) => ordered(a, b) }

您的方法的签名不方便。类型推断仅从一个参数列表流向下一个参数列表,但不在一个参数列表中,因此在您的情况下,编译器不会知道ordered 中的A 是什么,即使它已经知道A 是什么在as:

isSorted(Array(1, 5, 3, 4), (a, b) => a < b)
// error: missing parameter type
// isSorted(Array(1, 5, 3, 4), (a, b) => a < b)
//                              ^
// error: missing parameter type
// isSorted(Array(1, 5, 3, 4), (a, b) => a < b)
//                                 ^

你必须明确告诉编译器类型:

isSorted(Array(1, 5, 3, 4), (a: Int, b: Int) => a < b)
//=> res: Boolean = false

因此,最好将函数参数放在单独的参数列表中:

def isSorted[A](as: Array[A])(ordered: (A, A) => Boolean) = 
  as.sliding(2).forall { case Array(a, b) => ordered(a, b) }

现在,类型推断按预期工作:

isSorted(Array(1, 5, 3, 4))((a, b) => a < b)
//=> res: Boolean = false

您还可以使用占位符阻止函数的语法:

isSorted(Array(1, 5, 3, 4)) { _ < _ }
//=> res: Boolean = false

最后,签名实际上比必要的要严格得多:实际上没有什么要求 as 成为 Array,它可以与更通用的类型(例如 Seq)一起工作:

def isSorted[A](as: Seq[A])(ordered: (A, A) => Boolean) = 
  as.sliding(2).forall { case Seq(a, b) => ordered(a, b) }

现在,我们还可以传递List,例如,而不仅仅是Arrays。事实上,只要稍微改写一下这个方法,应该可以让它对所有Iterables工作。

【讨论】:

    【解决方案2】:

    使用返回关键字:

    @tailrec
      def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = {
        if (as.length == 0 || as.length == 1 || as.isEmpty) return true
        else if (!ordered(as(0), as(1))) return false
        isSorted(as.tail, ordered)
      }
    

    或者你可以这样做(推荐):

    @tailrec
          def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = {
            if (as.length == 0 || as.length == 1 || as.isEmpty) return true
            else if (!ordered(as(0), as(1))) return false
            else isSorted(as.tail, ordered)
          }
    

    【讨论】:

    • 没有。不要使用return。只需在isSorted() 递归之前添加else
    • 是的,这可以做到!但是使用return有什么问题呢?
    • 有几个微妙的reasons to avoid return。在某些情况下,它们可能会引入意外行为。避免return只是一个好习惯。
    猜你喜欢
    • 1970-01-01
    • 2021-11-23
    • 1970-01-01
    • 2017-11-20
    • 1970-01-01
    • 1970-01-01
    • 2010-09-19
    • 1970-01-01
    • 2021-11-06
    相关资源
    最近更新 更多