在本节中,我们将学习如何创建尾递归(tail recursive)函数,以及如何使用@annotation.tailrec注解,这将指示编译器应用任何进一步的优化。
如何定义尾递归函数?在下面的示例中,我们定义一个名为search()的尾部递归函数,它有以下输入参数:
-
fruitName:String类型,要在数组中搜索的商品项。
-
fruits:String[]数据类型,代表商品数组。
-
index:Int类型,查找要在其上运行搜索的Array内的索引。
object demo {
// @annotation.tailrec指示编译器添加与堆栈帧管理有关的任何优化,因为此函数是递归的。
@annotation.tailrec
def search(fruitName: String, fruits: Array[String], index: Int): Option[Boolean] = {
if(fruits.length == index) { // 对search函数的退出调用,因为我们已经遍历了Array中的所有元素
None
} else if(fruits(index) == fruitName) { // 将退出search函数,因为已经在Array中找到了搜索项
Some(true)
} else {
val nextIndex = index + 1 // 将当前索引增加1
search(fruitName, fruits, nextIndex) // 递归调用search函数
}
}
def main(args: Array[String]): Unit = {
// 首先定义一个String类型的Array来保存一些商品
val arrayDonuts = Array("苹果", "草莓", "水晶西瓜", "葡萄干")
// 调用尾递归函数
val found = search("苹果", arrayDonuts, 0)
println(s"发现苹果 = $found")
val notFound = search("巧克力", arrayDonuts, 0)
println(s"发现巧克力 = $notFound")
}
}
执行上面的代码,输出结果如下:
发现苹果 = Some(true)
发现巧克力 = None
调用尾部递归函数与调用任何其他函数没有太大区别,只需通过函数名调用该函数,并向其传递相应的参数。
尾递归函数将有助于防止调用堆栈中的溢出,因为循环结构的计算在每一步都发生。
在scala.util.control.TailCalls._包中,Scala为尾部递归提供了实用程序。利用这些实用工具我们可以创建尾部递归函数。从前面的内容可知,尾部递归函数将有助于防止调用堆栈中的溢出,因为循环构造的计算在每一步都发生。
如何使用scala.util.control.TailCalls._定义一个尾部递归函数?
当涉及到递归时,Scala在包scala.util.control.tailcalls._中提供了额外的实用程序。让我们继续,通过使用这些递归实用工具,重新编写上一步中的递归search()函数。
//import scala.util.control.TailCalls.TailRec
import scala.util.control.TailCalls._
object demo {
def main(args: Array[String]): Unit = {
// 首先定义一个String类型的Array来保存一些商品
val arrayDonuts = Array("苹果", "草莓", "水晶西瓜", "葡萄干")
// 调用对tailSearch的递归函数的语法略有不同,因为需要将调用封装在tailcall()中
val tailFound = tailcall(tailSearch("苹果", arrayDonuts, 0))
println(s"使用TailCall发现苹果 = ${tailFound.result}") // 注意:返回值需要通过调用result来获取
val tailNotFound = tailcall(tailSearch("巧克力", arrayDonuts, 0))
println(s"使用TailCall发现巧克力 = ${tailNotFound.result}")
}
// 使用scala.util.control.tailcalls._包中的实用程序创建尾递归函数
def tailSearch(donutName: String, donuts: Array[String], index: Int): TailRec[Option[Boolean]] = {
if(donuts.length == index) {
done(None) // 注意: done是从scala.util.control.TailCalls._中导入的
} else if(donuts(index) == donutName) {
done(Some(true))
} else {
val nextIndex = index + 1
// 注意: tailcall是从scala.util.control.TailCalls._中导入的
tailcall(tailSearch(donutName, donuts, nextIndex))
}
}
}
执行上面的代码,输出结果如下:
使用TailCall发现苹果 = Some(true)
使用TailCall发现巧克力 = None
trampoline tail recursive
假设有两个尾部递归函数F(A)和F(B),并且F(A)调用F(B),但反过来F(B)也调用F(A)。
那么F(A)被称为蹦床尾部递归函数,因为调用堆栈在F(A)和F(B)两个函数之间来回跳转—因此得名trampoline。
使用scala.util.control.TailCalls定义一个蹦床函数。
为了保持示例简单,我们将假设下面的notSweetDonut()函数将从其donutList参数中记录第一个甜甜圈。然后,它将把甜甜圈列表的尾巴传递回verySweetDonut()函数的tailcall(verySweetDonut(donutList.tail)) 函数verySweetDonut()将检查甜甜圈列表的头部,如果它没有找到一个甜甜甜圈,它将回调notSweetDonut()函数。在verySweetDonut()和nosweetdonut()函数之间来回跳转就是蹦床效应。
import scala.util.control.TailCalls.TailRec
import scala.util.control.TailCalls._
object demo {
def verySweetFruit(fruitList: List[String]): TailRec[Boolean] = {
println(s"verySweetFruit 函数: fruit list = $fruitList")
if (fruitList.isEmpty) {
println("verySweetFruit 函数: fruit list是空的, 返回 false")
done(false)
} else {
if(Set(fruitList.head).subsetOf(Set("苹果","西瓜","草莓"))) {
println(s"verySweetDonut 函数: 找到fruit list's head = ${fruitList.head}非常甜,返回true")
done(true)
} else {
println(s"verySweetDonut 函数: fruit list's head = ${fruitList.head}不甜, 将 fruit list转发给notSweetDonut 函数")
tailcall(notSweetFruit(fruitList))
}
}
}
def notSweetFruit(fruitList: List[String]): TailRec[Boolean] = {
println(s"notSweetDonut函数: fruit list = $fruitList")
if (fruitList.isEmpty) {
println("notSweetDonut 函数: fruit list是空的, 返回 false")
done(false)
} else {
println(s"notSweetDonut 函数: fruit list's head = ${fruitList.head}不甜, 将 fruit list's tail转发给verySweetDonut 函数")
tailcall(verySweetFruit(fruitList.tail))
}
}
def main(args: Array[String]): Unit = {
// 只需使用scala.util.control.TailCalls._中的tailcall()函数,
// 并传递给它verySweetDonut()函数,该函数接受一个String类型的甜甜圈列表。
val fruitList: List[String] = List("南瓜", "东瓜", "西瓜", "倭瓜")
val foundVerySweetDonut = tailcall(verySweetFruit(fruitList)).result
println(s"找到了非常甜的水果 = $foundVerySweetDonut")
}
}
执行以上代码,输出结果如下:
verySweetFruit 函数: fruit list = List(南瓜, 东瓜, 西瓜, 倭瓜)
verySweetDonut 函数: fruit list's head = 南瓜不甜, 将 fruit list转发给notSweetDonut 函数
notSweetDonut函数: fruit list = List(南瓜, 东瓜, 西瓜, 倭瓜)
notSweetDonut 函数: fruit list's head = 南瓜不甜, 将 fruit list's tail转发给verySweetDonut 函数
verySweetFruit 函数: fruit list = List(东瓜, 西瓜, 倭瓜)
verySweetDonut 函数: fruit list's head = 东瓜不甜, 将 fruit list转发给notSweetDonut 函数
notSweetDonut函数: fruit list = List(东瓜, 西瓜, 倭瓜)
notSweetDonut 函数: fruit list's head = 东瓜不甜, 将 fruit list's tail转发给verySweetDonut 函数
verySweetFruit 函数: fruit list = List(西瓜, 倭瓜)
verySweetDonut 函数: 找到fruit list's head = 西瓜非常甜,返回true
找到了非常甜的水果 = true
1 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
2 本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
3 本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
暂无评论内容