scala - 检查 Array[T] 是否在没有本地 tailrec 的情况下排序?
问题描述
实现 isSorted,它检查 Array[A] 是否根据给定的比较函数排序:
def isSorted[A](as: Array[A], 有序: (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。
解决方案
在 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 社区风格指南还建议在控制流关键字之后使用空格,例如if
orwhile
以清楚地将它们与方法调用区分开来:
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) }
您的方法的签名不方便。类型推断仅从一个参数列表流向下一个参数列表,但不在一个参数列表中,因此在您的情况下,编译器将不知道A
in 的内容ordered
,即使它已经知道A
in 的内容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
是 a Array
,它可以与更通用的类型一样工作,例如Seq
:
def isSorted[A](as: Seq[A])(ordered: (A, A) => Boolean) =
as.sliding(2).forall { case Seq(a, b) => ordered(a, b) }
现在,我们也可以传递一个List
,例如,而不是只传递Array
s。事实上,只要稍微改写一下方法,就应该可以让它对所有 Iterable
的 s 都有效。
推荐阅读
- javascript - Next.js 在 html 标记中呈现应用程序,以字符串形式出现
- google-app-engine - App Engine 中的身份验证方法
- node.js - 带有passportjs + Vue的谷歌oAuth
- flutter - 不考虑 minWidth 和 minHeight 的框约束
- html - 使用标签时如何制作幻灯片效果
- laravel - Laravel 6 - 访问器附加未在关系中调用
- javascript - 不使用被动侦听器来提高滚动性能(灯塔报告)
- javascript - IE浏览器的反应问题
- python - 我不能使用虚线在 seaborn 上绘制 2 个时间序列
- php - Codeigniter:使用 ajax 通过动态下拉列表更改状态