千家信息网

怎么使用Scala语言

发表于:2024-09-24 作者:千家信息网编辑
千家信息网最后更新 2024年09月24日,这篇文章主要讲解了"怎么使用Scala语言",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么使用Scala语言"吧!为什么递归会受到忽视为 了回答这一
千家信息网最后更新 2024年09月24日怎么使用Scala语言

这篇文章主要讲解了"怎么使用Scala语言",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么使用Scala语言"吧!

为什么递归会受到忽视

为 了回答这一问题,必须先说到编程范式。在所有的编程范式中,面向对象编程(Object-Oriented Programming)无疑是***的赢家。看看网上的招聘启事,无一例外,会要求应聘者熟练掌握面向对象编程。但其实面向对象编程并不是一种严格意义上 的编程范式,严格意义上的编程范式分为:命令式编程(Imperative Programming)、函数式编程(Functional Programming)和逻辑式编程(Logic Programming)。面向对象编程只是上述几种范式的一个交叉产物,更多的还是继承了命令式编程的基因。遗憾的是,在长期的教学过程中,只有命令式 编程得到了强调,那就是程序员要告诉计算机应该怎么做,而不是告诉计算机做什么。而递归则通过灵巧的函数定义,告诉计算机做什么。因此在使用命令式编程思 维的程序中,不得不说,这是现在多数程序采用的编程方式,递归出镜的几率很少,而在函数式编程中,大家可以随处见到递归的方式。下面,我们就通过实例,为 大家展示递归如何作为一种普遍方式,来解决编程问题的。

一组简单的例子

如何为一组整数数列求和?按照通常命令式编程的思 维,我们会采用循环,依次遍历列表中的每个元素进行累加,最终给出求和结果。这样的程序不难写,稍 微具备一点编程经验的人在一分钟之内就能写出来。这次我们换个思维,如何用递归的方式求和?为此,我们不妨把问题简化一点,假设数列包含 N 个数,如果我们已经知道了后续 N – 1 个数的和,那么整个数列的和即为***个数加上后续 N – 1 个数的和,依此类推,我们可以以同样的方式为 N – 1 个数继续求和,直到数列为空,显然,空数列的和为零。听起来复杂,事实上我们可以用一句话来总结:一个数列的和即为数列中的***个数加上由后续数字组成的 数列的和。现在,让我们用 Scala 语言把这个想法表达出来。

清单 1. 数列求和

//xs.head 返回列表里的头元素,即***个元素 //xs.tail 返回除头元素外的剩余元素组成的列表 def sum(xs: List[Int]): Int =  if (xs.isEmpty) 0 else xs.head + sum(xs.tail)

大家可以看到,我们只使用一行程序,就将上面求和的方法表达出来了,而且这一行程序看上去简单易懂。尽量少写代码,这也是 Scala 语言的设计哲学之一,较少的代码量意味着写起来更加容易,读起来更加易懂,同时代码出错的概率也会降低。同样的程序,使用 Scala 语言写出的代码量通常会比 Java 少一半甚至更多。

上述这个数列求和的例子并不是特别的,它代表了递归对于列表的一种普遍的处理方式,即对一个列表的操作,可转化为对***个元素,及剩余列表的相同操 作。比如我们可以用同样的方式求一个数列中的***值。我们假设已经知道了除***个元素外剩余数列的***值,那么整个数列的***值即为***个元素和剩余数列 ***值中的大者。这里需要注意的是对于一个空数列求***值是没有意义的,所以我们需要向外抛出一个异常。当数列只包含一个元素时,***值就为这个元素本 身,这种情况是我们这个递归的边界条件。一个递归算法,必须要有这样一个边界条件,否则会一直递归下去,形成死循环。

清单 2. 求***值

def max(xs: List[Int]): Int = {    if (xs.isEmpty)      throw new java.util.NoSuchElementException    if (xs.size == 1)      xs.head    else      if (xs.head > max(xs.tail)) xs.head else max(xs.tail) }v

同样的方式,我们也可以求一个数列中的最小值,作为一个练习,读者可下去自行实现。

让我们再看一个例子:如何反转一个字符串?比如给定一个字符串"abcd",经过反转之后变为 "dcba"。同样的,我们可以做一个大胆的假设,假设后续字符串已经反转过来,那么接上***个字符,整个字符串就反转过来了。对于一个只有一个字符的字符串,不需要反转,这是我们这个递归算法的边界条件。程序实现如下:

清单 3. 反转字符串

def reverse(xs: String): String = if (xs.length == 1) xs else reverse(xs.tail) + xs.head

***一个例子是经典的快速排序,读者可能会觉得这个例子算不上简单,但是我们会看到,使用递归的方式,再加上 Scala 简洁的语言特性,我们只需要短短几行程序,就可以实现快速排序算法。 快速排序算法的核心思想是:在一个无序列表中选择一个值,根据该值将列表分为两部分,比该值小的那一部分排在前面,比该值大的部分排在后面。对于这两部分 各自使用同样的方式进行排序,直到他们为空,显然,我们认为一个空的列表即为一个排好序的列表,这就是这个算法中的边界条件。为了方便起见,我们选择*** 个元素作为将列表分为两部分的值。程序实现如下:

清单 4. 快速排序

def quickSort(xs: List[Int]): List[Int] = {    if (xs.isEmpty) xs    else      quickSort(xs.filter(x=>xx>xs.head)) }

当然,为了使程序更加简洁,作者在这里使用了列表中的一些方法:给列表增加一个元素,连接两个列表以及过滤一个列表,并在其中使用了 lambda 表达式。但这一切都使程序变得更符合算法的核心思想,更加易读。

尾递归

从上面的例子中我们可以看到,使用递归方式写出的程序通常通俗易懂,这其实代表这两种编程范式的不同,命令式编程范式倾向于使用循环,告诉计算机怎 么做,而函数式编程范式则使用递归,告诉计算机做什么。习惯于命令式编程范式的程序员还有一个担忧:相比循环,递归不是存在效率问题吗?每一次递归调用, 都会分配一个新的函数栈,如果递归嵌套很深,容易出现栈溢出的问题。比如下面计算阶乘的递归程序:

清单 5. 递归求阶乘

def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1)

当递归调用 n – 1的阶乘时,由于需要保存前面的 n,必须分配一个新的函数栈,这样当 n很大时,函数栈将很快被耗尽。然而尾递归能帮我们解决这个问题,所谓尾递归是指在函数调用的***一步,只调用该递归函数本身,此时,由于无需记住其他变量,当前的函数栈可以被重复使用。上面的程序只需稍微改造一下,既可以变成尾递归式的程序,在效率上,和循环是等价的。

清单 6. 尾递归求阶乘

def factorial(n: Int): Int = {    @tailrec    def loop(acc: Int, n: Int): Int =      if (n == 0) acc else loop(n * acc, n - 1)      loop(1, n) }

在上面的程序中,我们在阶乘函数内部定义了一个新的递归函数,该函数***一步要么返回结果,要么调用该递归函数本身,所以这是一个尾递归函数。该函数多出一个变量 acc,每次递归调用都会更新该变量,直到递归边界条件满足时返回该值,即为***的计算结果。这是一种通用的将非尾递归函数转化为尾递归函数的方法,大家可多加练习,掌握这一方法。对于尾递归,Scala 语言特别增加了一个注释 @tailrec,该注释可以确保程序员写出的程序是正确的尾递归程序,如果由于疏忽大意,写出的不是一个尾递归程序,则编译器会报告一个编译错误,提醒程序员修改自己的代码。

一道面试题

也许有的读者看了上面的例子后,还是感到不能信服:虽然使用递归会让程序变得简洁易懂,但我用循环也一样可以实现,大不了多几行代码而已,而且我还 不用知道什么尾递归,写出的程序就是效率***的。那我们一起来看看下面这个问题:有趣的零钱兑换问题。题目大致如下:假设某国的货币有若干面值,现给一张 大面值的货币要兑换成零钱,问有多少种兑换方式。这个问题经常被各大公司作为一道面试题,不知难倒了多少同学,下面我给出该问题的递归解法,读者们可以试 试该问题的非递归解法,看看从程序的易读性,及代码数量上,两者会有多大差别。该问题的递归解法思路很简单:首先确定边界条件,如果要兑换的钱数为 0,那么返回 1,即只有一种兑换方法:没法兑换。这里要注意的是该问题计算所有的兑换方法,无法兑换也算一种方法。如果零钱种类为 0 或钱数小于 0,没有任何方式进行兑换,返回 0。我们可以把找零的方法分为两类:使用不包含***枚硬币(零钱)所有的零钱进行找零,使用包含***枚硬币(零钱)的所有零钱进行找零,两者之和即为所有 的找零方式。***种找零方式总共有 countChange(money, coins.tail)种,第二种找零方式等价为对于 money – conins.head进行同样的兑换,则这种兑换方式有 countChange(money - coins.head, coins)种,两者之和即为所有的零钱兑换方式。

清单 7. 零钱兑换问题的递归解法

def countChange(money: Int, coins: List[Int]): Int = {   if (money == 0)     1   else if (coins.size == 0 || money < 0)     0   else     countChange(money, coins.tail) + countChange(money - coins.head, coins) }

感谢各位的阅读,以上就是"怎么使用Scala语言"的内容了,经过本文的学习后,相信大家对怎么使用Scala语言这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

递归 程序 编程 函数 方式 数列 问题 元素 语言 范式 零钱 字符 方法 代码 例子 命令 命令式 清单 个数 字符串 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 大华阵列服务器进入网址 双十一怎么设置网络安全 常州兔仔互联网科技有限公司 技能大赛网络安全考试内容 数据库报表页脚不显示 余承东回应网络安全 三亚网络安全直播间 安徽服务器虚拟化部署服务器 网络安全常识十条 学生查询统计数据库设计 近期网络安全大会 黄石iptv服务器费用 access数据库有什么用 优秀软件开发主管感言 魔兽世界高级服务器配置要求 模拟软件开发 网络安全中为什么需要ids 昆明市西山区南亚汇网络技术部 服务器我用 南京最大的软件开发公司 深圳市互动网络技术有限公司 软件开发职中学费 黑马数据库学习文档 关于网络安全防诈骗的手抄报 昆山正规的模具制造管理软件开发 宝德服务器管理口用户密码 辅助dns服务器示意图 云服务器的多用户管理系统 阴阳师是不是只有一个大服务器 服务器增加服务会不会被发现
0