开发者学堂课程【Scala 核心编程 - 进阶:作业评讲】学习笔记,与课程紧密连接,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/610/detail/9128
作业评讲
内容介绍:
一、模式匹配
二、高阶函数
一、模式匹配
1.利用模式匹配,编写一个 swap 函数,接受一个整数的对偶,(也就是说,对偶里的元素都是 int 类型),返回对偶的两个组成部件互换位置的新对偶。
(1)思路
先进行匹配,然后进行case操作,最后返回。
(2)过程
def swap[S, T](tup: (S, T)) = {
tup match {
case(a,b)=> (b,a)
}
}
该题难度较小,但要注意的是在写对偶时,对偶的类型的写法如果对偶的元素值类型都是 int,可以将 def swap[S, T](tup: (S, T)) = {
改写为 def swap[S, T](tup: (Int, Int)) = { 。原来的代码更为灵活一些。
(3)代码演示
在chapter16中点击“New”“Package”新建一个包,命名为homework,再在该包中新建一个文件“Exercise01”,并设置为object。
编写函数:
package com.atguigu.chapter16.homework
object Exercise01
def main (args:Array[string]): Unit = {
}
//需求:利用模式匹配,编写一个 swap 么函数,接受一个整数的对偶,返回对偶的两个组成部件互换位置的新对偶。
def swap[T, S](tup: (T, S)): Unit = {
//此处 swap的泛型顺序可以随意写入,且函数最终要返回值,返回值的类型可以设定,但是也可以忽略,用类型推导式即可,即改为def swap[T, S](tup: (T, S)) = {。
tup match {
case(a,b)=> (b,a)
//匹配到a,b两个变量是自行写入的变量,case中的变量匹配,会默认将tup里的第一个值传给a,第二个值传给b,然后直接将其交换位置即可。
case _ => println("没有匹配到")
}
}
(4)代码测试
在搭建好函数之后,可以在前面的 object 部分输入具体的值,进行试验,如“println (swap((10,20))”,若返回值为(20,10),则正确。运行代码,返回的结果为(20,10)。
使用模式匹配可以非常轻松地实现两个数的交换,甚至是任意数的位置互换。
2.利用模式匹配,编写一个 swap 函数,交换数组中的前两个元素的位置,前提条件是数组长度要至少为2。
(1)代码演示
package com.atguigu.chapter16.homework
object Exercise02
def main (args:Array[string]): Unit = {
}
//需求:利用模式匹配,编写一个swap函数,交换数组中的前两个元素的位置,前提条件是数组长度要至少为2。
def swap(arr:Array[Any]) = {
//题目中要匹配两个数组,那可以通过直接接收数组解决。而此处并没与指定数组的类型,可以默认为是any。同样可以使用类型推导,省略“: Unit”。
array match {
case Array(first,second,rest @_*) =>Array(second,first) ++ rest
//之前有学习过数组匹配的方法,如果数组有两个值为first、second,后续的匹配可以用rest @_*。
题目中要求前两个位置变化,即此时定义的数组至少有两个值,返回时,直接将前面的两个值second和first进行交换,后面的值不动即可。
case _ => arr
//若没有匹配到(数组长度不大于2,即不满足交换的条件),就按照原来的值返回
}
}
(2)代码测试
在搭建好函数之后,可以在前面的object部分中任意编写一个数组(数组类型不做要求),进行试验。
如“println(swap(Array(1,2,"hello"))) ”,若返回值为(2,1,"hello"),则代码正确。但按照目前的代码输出结果无法显示,则可以将“println(swap(Array(1,2,"hello"))) ”改为“println(swap(Array(1,2,"hello")).toBuffer) ”,最终结果显示为(2,1,"hello")。
若数组长度不够,如“println(swap(Array("tom"))) ”,数组中只有tom,也就不存在前两个值的交换,就会将原来的值直接返回返回值为"tom"。
3.编写一个函数,计算List[Option[Int]]中所有非None值之和,不得使用match语句。
(1)思路分析
List 中的值为 Option,Option的值类型为Int,不得使用match语句来计算其中非None值之和。我们之前学过getOrElse,这个语句可以解决该问题。
具体过程为:
通过遍历之后,通过_.getOrElse操作,如该值为None,则输出值为0,若该值不是None,则输出该值,因此通过该操作,输出值为通过_.getOrElse(0)+…。
(2)代码演示
在 homework 中新建一个文件“Exercise03”,并设置为object。编写函数:
package com.atguigu.chapter16.homework
object Exercise03
def main (args:Array[string]): Unit = {
}
//需求:返回非None的和
def main(args:Array[String]):Unit={
val lists=List(Some(1),Some(3),None,Some(10),None)
println(mysum(lists)) //14
}
def mysum(lst: List[Option[Int]])=Ist.map(_·getOrElse(0)).
sum
//x的值中包含 List,最后接收了 List(option)之后遍历
//理解Ist.map(_·getOrElse(0)).sum:
若此处输入Ist.map(),系统提示Ist.map()中传入的值是Option[Int],即严格来写应为Ist.map((x:Option[Int]) =>x._getOrElse(0)).sum(其中x:Option[Int]是传入的形参,而“=>”右侧则是函数的函数体,即如果能得到即本身不是None,则返回本身的值,如果不能得到即本身是None,则返回0。在返回之后,对返回值求和)
//结合上方的“val lists=List(Some(1),Some(3),None,Some(10),None)”,当将lists传给mysum之后,即会使用getOrElse()语句遍历List中的各个值,若该值为1,则输出1,若为3,则输出3,若为None,则输出0,最终输出的集合应为1 3 0 10 0,则最终经过sum之后结果为14。
但是这种写法有些冗余,可以对之进行简化。因为该条代码中可以进行类型推导,因此“Ist.map((x:Option[Int]) ”中的“:Option[Int]”可以省略,即直接简化为“Ist.map((x) ) =>x._getOrElse(0)).sum”;此外对于x,在整条代码中相当于只出现过一次,因此,“(x) ) =>x”亦可省略,即简化为“Ist.map(._getOrElse(0)).sum”
}
二、高阶函数
1、编写一个 compose 函数,将两个类型为Double=>Option Double的函数组合在一起,产生另一个同样类型的函数。
如果其中一个函数返回None,则组合函数也应返回None。例如:
def f(x:Double)=if(x>0)Some(sqrt(x))else None
def g(x:Double)=if(x!=1)Some(1/(x-1))else None
val h=compose(f.g)
h(2)将得到Some(1),而h(1)和h(0)将得到None
(1)思路分析
本题的主要难度在于审题,compose函数即合并函数,合并的目的在于将两个函数组合在一起,且这两个函数都是接收Double,并返回Option[Double]的函数,即将这两个函数整合在一起产生另外一个同样类型的新的函数。
对于新的函数来说,如果整合之前的两个函数里其中一个函数为None,则新函数返回None。
下面一段代码的含义是:
已知两个函数关于x的函数f(x)和g(x)。其中第一条代码def f(x:Double)=if(x>0)Some(sqrt(x))else None的含义是f(x)函数可以接受Double,“if(x>0)Some(sqrt(x))else None”为其函数体,意义是如果x大于0,就返回sqrt(x),否则就返回None;第二条代码def g(x:Double)=if(x!=1)Some(1/(x-1))else None的含义是g(x)函数可以接受Double,if(x!=1)Some(1/(x-1))else None为其函数体,意义是如果x不等于1,则返回1/(x-1),否则(x=1)返回None。最后val h=compose(f.g)给出了我们需要完善的compose函数的格式,它是一个高阶函数。
最后题目中又给出了一个案例,如果传入“2”,则返回值为Some(1);若传入“1”或“0”,则返回值为None。
总之,本题的最终目的在于将两个给出的函数合并在一起,返回h,h在两个函数中通用。
(2)代码演示
在 homework 中新建一个文件“Exercise04”,并设置为 object。编写函数:
def main(args: Array[String]):Unit={
//引入所需的包
import scala.math.sqrt
val h=compose(f,g)
println(h(2))
}
//给出的两个函数
def f(x:Double)=if(x>0)Some(sqrt(x))else None
def g(x:Doable)=if(x!=1)Some(1/(x-1))else None
//根据需求合并为一个函数,形式为compose(f.g)
//案例:h(2)将得到Some(1),而h(1)和h(0)将得到None,即在合并的函数中传入2,输出Some(1)。再具体分析,“2”应该是g(x)函数输出的值,因为要想合并函数输出值不是None,则两个函数的输出值都不能是None。
若2是由f(x)得出,则返回值为sqrt(x),则返回值与题目不符,因此h(2)将得到Some(1)是通过g(x)函数得出的。
def compose(f:Double=>Option[Double],g:Double=>Option[
Double])={
//f:Double=>Option[Double]与g:Double=>Option[Double]
是形参的格式
(x:Double)=>
//返回了一个匿名函数,x:Double 为匿名函数的形参,因为返值
也是 Double;
//合并
//1.只要有一个函数返回None,组合函数也返回None
if(f(x) == None| g(x) == None) None
//假如f(x)返回None,或者g(x)返回None,则组合函数返回None
//2.如果新函数传入的值为2,则说明该函数是由g(x)函数得出的
3.代码测试
此处进行代码的传递即可。
val h = compose(f,g)
println(h(2)) //1
println(h(1) +""+h(0)) //None和None
运行代码一下,返回值为1.0 None None。若之前的函数中输入为else f(x),则输出结果为1.414…,则不符合案例提示。
2.编写函数values(fun:(Int)=>Int,low:Int,high:Int),该函数输出一个集合,对应给定区间内给定函数的输入和输出。
比如,values(x=>x*x,-5.5)应该产出一个对偶的集合(-5,25),(-4,16),(-3,9),…,(5,25)
(1)思路分析
values(fun:(Int)=>Int,low:Int,high:Int) //给出了函数形式
该高阶函数的作用是输出一个集合,这个集合可以对应给定区间内按照给定函数的输入和输出,即最小值和最大值相当于一个low-to-high的区间。
即通过fun函数对值进行处理,会得出一个对偶,该对偶的值为(low,fun(x)),且处理一个值即会返回一个值,最终形成一个集合。也就是最终的values会遍历low-to-high区间,map该fun函数,且该fun函数会返回一个对偶,最终进行组合。
(2)代码演示
在homework中新建一个文件“Exercise05”,并设置为object。
package com.atguigu.chapter16.homework
object Exercise05 {
def main(args:Array[String]):Unit={
}
//需求:
编写函数values(fun:(Int)=>Int,low:Int,high:Int),该函数输出一个集合,对应给定区间内给定函数的输入和输出。比如,values(x=>x*x,-5.5)应该产出一个对偶的集合(-5,25),(-4,16),(-3,9),…,(5,25)
def values(fun:(Int)=>Int,low:Int,high:nt)={
//函数的形式
var newlist=List[(Int,Int)]()
//由于最终返回值是一个集合,因此定义了一个空的List,准备放入对偶
low to high foreach {
//进行遍历low to high
//以下为要处理的函数
item = > // item为遍历得到的值
newlist = ( item,fun( item)) : :newlist
//将遍历得到的值交给函数fun( item)处理,然后进行组合拼接
}
newlist //返回
}
(3)代码测试
//函数形式x=> x*x,因此在调用时按照该式子调用即可
println (values(x:Int) = >x*x,-5,5))
//而 newlist = ( item,fun( item)) : :newlist原先为空的集合,经过第一次处理结果为: newlist = (-5,25) : :List() =>(-5,25);
经过第二次处理结果为: newlist = (-4,16) : :List((-5,25)) => (-5,25),(-4,16);(注:新的结果会加在前方)
……
运行代码,最终的输出结果为(5,25),(4,16),(3,9),…,(-4,16),(-5,25),发现此时输出的结果顺序不一致。为得出与案例中一致的顺序,可以改变传入的值的顺序,即
println (values(x:Int) = >x*x,-5,5,reverse))
再次运行代码,结果为(-5,25),(-4,16),(-3,9),…,(5,25)。
3、如何用 reduceLeft 得到数组Array(1,333,4,6,4,4,9,32,6,9,0,2)中的最大元素?
object Exerciseo06 {
def main(args: Array[String]):Unit={
val arr = Array(1,333,4,6,4,4,9,32,6,9,0,2)
//改写下,
print(arr.reduceLeft((l,r)=>if(I>=r)1 else r))
}
def f1(l:Int,r:Int): Int = {
// f1接收两个值l与r
if(l>r) l else r
//如果l>r,则返回l;否则返回r。即返回较大的值。
}
}
4、用to和reduceLeft实现阶乘函数,不得使用循环或递归
def factorial(n:Int): Int= {
1 to n reduceLeft(_*_)
}
(1)代码演示:
在homework中新建一个文件“Exercise06”,并设置为object。
package com.atguigu.chapter16.homework
object Exercise06 {
def main(args:Array[String]):Unit={
}
def factorial(n:Int): Int= {
1 to n reduceLeft(_*_)
//以上为简写,全写应为1 to n reduceLeft((x:Int,y:Int) => x*y,因为reduce为二元函数,因此应接收两个值。由于可以进行类型推导,且x与y分别均只出现过一次,因而可以省略。
}
}
(2)代码测试
println(factorial(3)) // 1*2*3=6
运行代码,结果为6,说明代码无误。
4.编写函数 largest(fun:(Int)=>Int,inputs:Seq[Int]),输出在给定输入序列中给定函数的最大值。
举例来说,largest(x=>10*x-x*x,1 to10)应该返回25,不得使用循环或递归。
(1)思路分析
本题的难点在于审题接收函数。题目中表明新函数接收的值类型是Int,返回的值类型也是Int。
但要求的是input集合里面的最大值。即输出在给定输入序列中给定的函数的最大值,也就是说,要将imputs中的元素分别传给fun函数,从统计结果中输出最大值。
(2)代码演示
在 homework 中新建一个文件“Exercise07”,并设置为object。
package com.atguigu.chapter16.homework
object Exercise07 {
def main(args:Array[String]):Unit={
}
//需求:
①编写函数 largest(fun:(Int)=>Int,inputs:Seq[Int]),
//largest是一个高级函数,它可以接收函数fun:(Int)=>Int,及一个序列inputs:Seq[Int]
②输出在给定输入序列(inputs)中给定函数(fun:(Int)=>Int)的最大值。即将inputs中的每个元素传递给fun函数中进行计算,返回最大值
③如传入的函数是largest(x=>10*x-x*x,1 to10),其中x是该函数的形参,函数体是10*x-x*x,x=>10*x-x*x表示接收到的匿名函数,1 to10为imputs,应返回值为25
④不得使用循环或递归,即要求使用高阶函数来完成
实际上就是遍历,map求最大值。
A.课堂版(较易理解)
def largest(fun:(Int)=>Int,inputs:Seq[Int]) = {
inputs.map((n:Int) => fun(n)).max
//inputs.map((n:Int) => fun(n))的结果为将1到10依次传入fun(n)中得到的值的集合,求这个集合的最大值即可解决这个问题
//同时,n在该代码中也相当于只出现了一次,可省略为inputs.map( fun(_)).max
}
}
(3)代码测试
val maxval =largest(x=>10*x-x*x,1 to 10)
println("maxval"+maxval)
运行代码,最后结果是maxval=25。
课件版
def largest1(fun:(Int)=>Int,inputs:Seq[Int])= inputs.foldLeft(1)
((a,b)=> if(fun(b)>a) fun(b) else a)
deflargest2(fun:(Int)=>Int,inputs:Seq[Int])=inputs.map(fun(_)
).max
println(largest1(x=>10* x-x*x,1 to 10))
println(largest2(x=>10* x-x*x,1to10))
5.要得到一个序列的对偶很容易,比如:
val pairs =(1 to 10) zip (11 to 20)
编写函数adjustToPair,该函数接受一个类型为(Int,Int)=>Int的函数作为参数,并返回一个等效的,可以以对偶作为参数的函数。举例来说就是:adjustToPair(_*_)((6,7))应得到42,然后用这个函数通过map计算出各个对偶的元素之和
def ajustToPair(fun:(Int,Int)=>Int)=(x:(Int,Int))=>fun(x._1,x._2) val x=ajustToPair(_*_)((6, 7))
println(x)
val pairs=(1 to 10) zip (11 to 20)
println(pairs)
val y=pairs.map(ajustToPair(_+_))
println(y)
6.实现一个 unless 控制抽象,工作机制类似if,但条件是反过来的。
def unless(condition:=>Boolean)(block:=>Unit){if(!condition){b
lock}}
unless (0>1){ println("Unless!") }
unless我们学习过,在控制抽象里,学习过实现well循环,写法也完全相同。
“=>Boolean)(block:=>Unit”就是传入一个代码块,0>1传了一个代码块,println("Unless!")就是一个代码块,不再详细讲述,可以回顾控制抽像。