十三、函数式编程高级
13.1 偏函数(partial function)
13.1.1 提出一个需求,引起思考
给你一个集合
val list = List(1, 2, 3, 4, "abc")
,请完成如下要求
:
1)
将集合
list
中的所有数字
+1
,并返回一个新的集合
2)
要求忽略掉 非数字 的元素,即返回的 新的集合 形式为
(2, 3, 4, 5)
13.1.2 解决方式-filter + map 返回新的集合, 引出偏函数
/*给你一个集合 val list = List(1, 2, 3, 4, "abc") ,请完成如下要求:
1) 将集合 list 中的所有数字+1,并返回一个新的集合
2) 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)*/
object PartialFunDemo01 {
def main(args: Array[String]): Unit = {
//思路 1 filter + map 方式解决
//虽然可以解决问题,但是麻烦
val list =List(1,2,3,4,"abc")
//先过滤,在map,因为在map时list中有其他类型的
println(list.filter(f1)) //List(1, 2, 3, 4)此时都是Int类型了
//但是还不能直接map,因为list元素类型时Any,不会因为没有其他类型元素而改变
val list2 = list.filter(f1).map(f2).map(f3)
println(list2) //List(2, 3, 4, 5)
}
//先进行筛选,选出Int类型的
def f1(n:Any):Boolean={
n.isInstanceOf[Int]
}
//在进行转型,将Any类型转成Int类型
def f2(n:Any):Int={
n.asInstanceOf[Int]
}
//在进行需要的业务逻辑
def f3(n:Int):Int={
n+1
}
}
13.1.3 解决方式-模式匹配
object PartialFunDemo02 {
def main(args: Array[String]): Unit = {
//使用模式匹配
//思路 2-模式匹配
//小结:虽然使用模式匹配比较简单,但是不够完美
val list =List(1,2,3,4,"hello")
val list2=list.map(addOne2)
println(list2) //List(2, 3, 4, 5, ())
}
def addOne2(i:Any):Any={
i match {
case x:Int =>x+1
case _ =>
}
}
}
13.1.4 偏函数快速入门
object PartialFunDemo03 {
def main(args: Array[String]): Unit = {
//使用偏函数解决
val list = List(1, 2, 3, 4, "hello")
//定义一个偏函数
val partialFun = new PartialFunction[Any, Int] {
//定义一个偏函数
//1. PartialFunction[Any,Int] 表示偏函数接收的参数类型是 Any,返回类型是 Int
//2. isDefinedAt(x: Any) 如果返回 true ,就会去调用 apply 构建对象实例,如果是 false,过滤
//3. apply 构造器 ,对传入的值 + 1,并返回(新的集合)
override def isDefinedAt(x: Any): Boolean = if (x.isInstanceOf[Int]) true else false //直接x.isInstanceof[Int]一个意思
override def apply(v1: Any): Int = {
v1.asInstanceOf[Int] + 1
}
}
//使用偏函数
//说明:如果是使用偏函数,则不能使用 map,应该使用 collect
//说明一下偏函数的执行流程
//1. 遍历 list 所有元素
//2. 然后调用 val element = if(partialFun-isDefinedAt(list 单个元素)) {partialFun-apply(list 单个元素) }
//3. 每得到一个 element,放入到新的集合,最后返回
val list2 = list.collect(partialFun)
println("list2=" + list2) //list2=List(2, 3, 4, 5)
}
}
13.1.5 偏函数的小结
1)
使用构建特质的实现类
(
使用的方式是
PartialFunction
的匿名子类
)
2) PartialFunction
是个特质
(
看源码
)
3)
构建偏函数时,参数形式[Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数
4)
当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行
isDefinedAt()
如果为
true ,
就会执行
apply,
构建一个新的
Int
对象返回
5)
执行
isDefinedAt()
为
false
就过滤掉这个元素,即不构建新的
Int
对象
.
6) map
函数不支持偏函数,因为
map
底层的机制就是所有循环遍历,无法过滤处理原来集合的元
素
7) collect
函数支持偏函数
13.1.6 偏函数的简写形式
object PartialFunDemo04 {
def main(args: Array[String]): Unit = {
//可以将前面的案例的偏函数简写
def partialFun2 : PartialFunction[Any,Int] ={
case i:Int => i+1
case j:Double =>(j*10).toInt
}
val list =List(1,2,3.2,4.2,"hello")
val list2 = list.collect(partialFun2)
println(list2) //List(2, 3, 32, 42)
//第二种简写方式
val list3=list.collect{case i:Int => i+1}
println("list3="+list3) //list3=List(2, 3)
}
}
13.2 作为参数的函数
13.2.1 基本介绍
函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:
function1
,即:
(
参
数类型
) =>
返回类型
13.2.2 应用实例
object FunParameter {
def main(args: Array[String]): Unit = {
def plus(x:Int)=3+x
val result1=Array(1,2,3,4).map(plus(_))
println(result1.mkString(",")) //4,5,6,7
//说明
//1. 在 scala 中,函数也是有类型,比如 plus 就是 <function1>
}
}
13.2.3 对代码的小结
1) map(plus(_))
中的
plus(_)
就是将
plus
这个函数当做一个参数传给了
map
,
_
这里代表从集合中
遍历出来的一个元素。
2) plus(_)
这里也可以写成
plus
表示对
Array(1,2,3,4)
遍历,将每次遍历的元素传给
plus
的
x
3)
进行
3 + x
运算后,返回新的
Int
,并加入到新的集合
result1
中
4) def map[B, That](f: A => B)
的声明中的
f: A => B
一个函数
13.3 匿名函数
13.3.1 基本介绍
没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数
13.3.2 应用案例
object AnonymouseFunction {
def main(args: Array[String]): Unit = {
//对匿名函数的说明
//1. 不需要写 def 函数名
//2. 不需要写返回类型,使用类型推导
//3. = 变成 =>
//4. 如果有多行,则使用{} 包括
val triple= (x:Double)=>3*x
println("triple="+triple(3)) //triple=9.0
}
}
13.3.3 课堂案例
请编写一个匿名函数,可以返回
2
个整数的和,并输出该匿名函数的类型
val f1 = (n1: Int, n2: Int ) => {
println("匿名函数被调用")
n1 + n2
}
println("f1 类型=" + f1)
println(f1(10, 30))
13.4 高阶函数
13.4.1 基本介绍
能够接受函数作为参数的函数,叫做高阶函数
(higher-order function)
。可使应用程序更加健壮。
13.4.2 高阶函数基本使用
object HigherOrderFunction {
def main(args: Array[String]): Unit = {
def test(f:Double => Double,f2:Double =>Int,n1:Double) ={
f(f2(n1))
}
def sum(d:Double):Double={
d+d
}
def mod(d:Double):Int={
d.toInt %2
}
val res = test(sum,mod, 6.0)
println("res="+res) //12.0
}
}
13.4.3 高阶函数可以返回函数类型
object HigherOrderFunction2 {
def main(args: Array[String]): Unit = {
//说明
//1. minusxy 是高阶函数,因为它返回匿名函数
//2. 返回的匿名函数 (y: Int) => x - y
//3. 返回的匿名函数可以使用变量接收
def minusxy(x:Int)={
(y:Int) =>x-y //匿名函数
}
//分步执行
//f1 就是 (y: Int) => 3 - y
val f1 = minusxy(3)
println("f1 的类型=" + f1) //f1 的类型=org.example.chapter13.HigherOrderFunction2$$$Lambda$1/1329552164@282ba1e
println(f1(1)) // 2
println(f1(9)) // -6
//也可以一步到位的调用
println(minusxy(4)(9)) // -5
}
}
13.5 参数(类型)推断
13.5.1 基本介绍
参数推断省去类型信息(在某些情况下
[
需要有应用场景
]
,参数类型是可以推断出来的,如
list=(1,2,3) list.map() map 中函数参数类型是可以推断的
)
,同时也可以进行相应的简写。
13.5.2 参数类型推断写法说明
object ParameterInfer {
def main(args: Array[String]): Unit = {
val list=List(1,2,3,4)
println(list.map((x:Int)=>x+1)) //List(2, 3, 4, 5)
println(list.map((x)=>x+1)) //List(2, 3, 4, 5)
println(list.map(x=>x+1)) //List(2, 3, 4, 5)
println(list.map(_+1)) //List(2, 3, 4, 5) _这里代表从集合中遍历出来的一个元素。
val res=list.reduce(_+_)
println(res) //10
println(list.reduce(f1)) // 10
println(list.reduce((n1:Int ,n2:Int) => n1 + n2)) //10
println(list.reduce((n1 ,n2) => n1 + n2)) //10
println(list.reduce( _ + _)) //10
}
def f1(n1:Int ,n2:Int): Int = {
n1 + n2
}
}
13.6 闭包(closure)
13.6.1 基本介绍
基本介绍:闭包就是
一个函数
和
与其相关的引用环境
组合的一个
整体
(
实体
)
。
13.6.2 案例演示
//1.用等价理解方式改写 2.对象属性理解
def minusxy(x: Int) = (y: Int) => x - y
//f 函数就是闭包. val f = minusxy(20)
println("f(1)=" + f(1)) // 19
println("f(2)=" + f(2)) // 18
对上面代码的小结和说明
1)
第
1
点 (y: Int) => x –
y 返回的是一个匿名函数 ,因为该函数引用到到函数外的 x,
那么该函数和
x
整体形成一个闭包 如:这里 val f = minusxy(20)
的
f
函数就是闭包
2)
你可以这样理解,返回函数是一个对象,而
x
就是该对象的一个字段,他们共同形成一个闭包
3)
当多次调用
f
时(可以理解多次调用闭包),发现使用的是同一个
x,
所以
x
不变。
4)
在使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为他们会组合成一个整体
(
实
体
),
形成一个闭包
13.6.3 闭包的最佳实践
请编写一个程序,具体要求如下
1)
编写一个函数
makeSuffix(suffix: String)
可以接收一个文件后缀名
(
比如
.jpg)
,并返回一个闭
2)
调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀
(
比如
.jpg) ,
则返回 文件名
.jpg ,
如果已经有
.jpg
后缀,则返回原文件名。
3)
要求使用闭包的方式完成
String.endsWith(xx)
object ClosureDemo {
def main(args: Array[String]): Unit = {
//使用并测试
val f=makeSuffix(".jpg")
println(f("hello.jpg")) //hello.jpg
println(f("cat.jpg")) //cat.jpg
}
def makeSuffix(suffix:String)={
//返回一个匿名函数,会使用到suffix
(filename:String) =>{
if(filename.endsWith(suffix)) filename else filename+suffix
}
}
}
13.7 函数柯里化(curry)
13.7.1 基本介绍
1)
函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里
化
2)
柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里
化操作。
3)
不用设立柯里化存在的意义这样的命题。柯里化就是以函数为主体这种思想发展的必然产生的
结果。
(
即:柯里化是面向函数思想的必然产生结果
)
13.7.2 函数柯里化快速入门
编写一个函数,接收两个整数,可以返回两个数的乘积,要求
:
使用常规的方式完成
使用闭包的方式完成
使用函数柯里化完成
注意观察编程方式的变化
//说明
//使用平常方法
def mul(x: Int, y: Int) = x * y
println(mul(10, 10)) //100
//使用闭包
def mulCurry(x: Int) = (y: Int) => x * y
println(mulCurry(10)(9)) //90
//使用柯里化
def mulCurry2(x: Int)(y:Int) = x * y
println(mulCurry2(10)(8)) //80
13.7.3 函数柯里化最佳实践
比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
1)
全部转大写(或小写)
2)
比较是否相等
针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)
使用函数柯里化的思想来任务
object CurryDemo02 {
def main(args: Array[String]): Unit = {
def eq(s1:String,s2:String):Boolean={
s1.equals(s2)
}
//隐式类
implicit class TestEq(s:String){
//体现了将比较字符串的事情,分解成两个任务完成
//1. checkEq 完转换大小写
//2. f 函数完成比较任务
def checkEq(ss:String)(f:(String,String)=>Boolean):Boolean={
f(s.toLowerCase,s.toLowerCase)
}
}
val str1="hello"
println(str1.checkEq("HELLO")(eq)) //true
//在看一个简写形式
println(str1.checkEq("HELLO")((s1:String,s2:String)=>s1.equals(s2))) //true
println(str1.checkEq("HeLLO")(_.equals(_))) //true
}
}
13.8 控制抽象
13.8.1 看一个需求
如何实现将一段代码
(
从形式上看
)
,作为参数传递给高阶函数,在高阶函数内部执行这段代码
.
其
使用的形式如
breakable{}
。
var n = 10
breakable {
while (n <= 20) {
n += 1
if (n == 18) {
break()
}
}
}
13.8.2 控制抽象基本介绍
控制抽象是这样的函数,满足如下条件
1)
参数是函数
2)
函数参数没有输入值也没有返回值
控制抽象的应用案例(使用控制抽象实现了
while
语法)
object AbstractControl {
def main(args: Array[String]): Unit = {
//myRunInThread 就是一个抽象控制
//是没有输入, 也没有输出的函数 f1: () => Unit
//没有输入也没有输出的匿名函数传给了myRunInThread
def myRunInThread(f1:() => Unit)={
new Thread{
override def run(): Unit = {
f1()
}
}.start()
}
myRunInThread {
() =>
println("干活咯 ! 5秒完成")
Thread.sleep(5000)
println("干完咯!")
// 干活咯 ! 5秒完成
// 干完咯!
}
def myRunInThread2(f1: => Unit)={
new Thread{
override def run(): Unit = {
f1
}
}.start()
}
//对于没有输入,也没有返回值函数,可以简写成如下形式
myRunInThread2{
println("干活咯2 ! 5秒完成")
Thread.sleep(5000)
println("干完咯2!")
// 干活咯2 ! 5秒完成
// 干完咯2!
}
}
}
13.8.3 进阶用法:实现类似 while 的 until 函数
object ContriAbstractApp {
def main(args: Array[String]): Unit = {
var x=10
// while(x>10){
// x-=1
// println("x="+x)
// }
x=10
until(x>0){
x-=1
println("until x="+x)
// until x=9
// until x=8
// until x=7
// until x=6
// until x=5
// until x=4
// until x=3
// until x=2
// until x=1
// until x=0
}
}
//我们可以使用控制抽象写出until函数,实现类的效果
def until(condition: =>Boolean)(block: =>Unit):Unit={
//类似while循环,递归
if(condition){
block
//递归调用until
until(condition)(block)
}
}
}
十四、使用递归的方式去思考,去编程
14.1 基本介绍
Scala
是运行在
Java
虚拟机(
Java Virtual Machine
)之上,因此具有如下特点
:
1) 轻松实现和丰富的
Java
类库互联互通。
2) 它既支持面向对象的编程方式,又支持函数式编程。
3) 它写出的程序像动态语言一样简洁,但事实上它确是严格意义上的静态语言。
4) Scala 就像一位
武林中的集大成者
,将过去几十年计算机语言发展历史中的精萃集于一身,化繁为简,为程序员们提供了一种新的选择。设计者马丁·奥得斯基 希望程序员们将编程作为简洁,高效,令人愉快的工作。同时也让程序员们进行关于编程思想的新的思考
14.2 Scala 提倡函数式编程(递归思想)
先说下编程范式
:
1) 在所有的编程范式中,面向对象编程(
Object-Oriented Programming
)无疑是最大的赢家。
2) 但其实面向对象编程并不是一种严格意义上的编程范式,严格意义上的编程范式分为:命令式编程(Imperative Programming
)、函数式编程(
Functional Programming
)和逻辑式编程(
LogicProgramming)。
面向对象编程只是上述几种范式的一个交叉产物
,更多的还是继承了命令式编程的基因。
3)
在传统的语言设计中,只有命令式编程得到了强调,那就是程序员要告诉计算机应该怎么做。
而递归则通过灵巧的函数定义,
告诉计算机做什么
。因此在使用命令式编程思维的程序中,是现在多数程序采用的编程方式,递归出镜的几率很少,而在函数式编程中,大家可以随处见到递归的方式。
14.3 应用实例
scala
中循环不建议使用
while
和
do...while,
而建议使用递归。
14.3.1 应用实例要求
计算 1-50 的和
14.3.2 常规的解决方式
object RecursiveDemo01 {
def main(args: Array[String]): Unit = {
//传统方法完成 1-50 的求和任务
val now: Date = new Date()
val dateFormat: SimpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val date = dateFormat.format(now)
println("date=" + date) //输出时间
var res = BigInt(0)
var num = BigInt(1)
var maxVal = BigInt(99999999l) //BigInt(99999999l)[测试效率大数]
while (num <= maxVal) {
res += num
num += 1
}
println("res=" + res) //结果,耗时8秒
//再一次输出时间
val now2: Date = new Date()
val date2 = dateFormat.format(now2)
println("date2=" + date2) //输出时间
}
}
14.3.3 使用函数式编程方式-递归
函数式编程的重要思想就是尽量不要产生额外的影响,上面的代码就不符合函数式编程的思想, 下 面我们看看使用函数式编程方式来解决(Scala 提倡的方式)
测试:看看递归的速度是否有影响? 没有任何影响
object RecursiveDemo02 {
def main(args: Array[String]): Unit = {
//执行前的实践
val now:Date = new Date()
val dataFormat:SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val date = dataFormat.format(now)
println("执行前的实践date="+date) //执行前的实践date=2023-07-27 09:56:32
//测试耗时
var num = BigInt(1)
var sum=BigInt(0)
var res = mx(num, sum)//调用
println("res="+res)//结果 res=49999995000000
//执行后的之间
val now2:Date = new Date()
val date2 = dataFormat.format(now2)
println("执行前的实践date="+date2) //执行前的实践date=2023-07-27 09:56:33
}
//使用递归方式来统计1+...+num和
def mx(num:BigInt,sum:BigInt):BigInt={
if(num<=99999999l) return mx(num+1,sum+num)
else return sum
}
}
14.4 应用案例 2
求最大值
object ResucrsiveMaxList {
def main(args: Array[String]): Unit = {
println(max(List(1, 1, -9, 100,78)))
}
//求最大值
def max(xs:List[Int]):Int={
if(xs.isEmpty)
throw new NoSuchElementException
if(xs.size==1)
xs.head
//如果都不满足,则将xs.tail返回成心得List,在来调用max函数进行比较
else if(xs.head>max(xs.tail)) xs.head else max(xs.tail)
}
}
14.5 使用函数式编程方式-字符串翻转
object RecursiveReverseString {
def main(args: Array[String]): Unit = {
println(reverse("zxcvbnm"))
}
//使用递归完成字符串的翻转
def reverse(xs:String):String={
if(xs.length==1)
xs
else
reverse(xs.tail)+xs.head
}
}
14.6 使用递归-求阶乘
object RecursiveFactoria {
def main(args: Array[String]): Unit = {
println(factorial(5)) //120
}
//求出阶乘
def factorial(n:Int):Int={
if(n==0 ||n<0) 0 else n*factorial(n-1)
}
}
14.7 斐波那契数列
object RecursiveFbn {
def main(args: Array[String]): Unit = {
var count=BigInt(1)
println(fbn(5)) //5
println(fbn(3)) //2
println(fbn(10)) //55
println(fbn(20)) //6765
println(fbn(21)) //10946
println("20递归的次数是:"+count) //20递归的次数是:13530
println("21递归的次数是:"+count) //20递归的次数是:21892
//编写斐波那契数列
//研究下递归求斐波拉契的数的递归次数的增长情况
def fbn(n:BigInt):BigInt={
count+=1
if(n==1 || n==2) 1
else fbn(n-1)+fbn(n-2)
}
}
}
注意事项:当要重复计算时,使用递归要考虑优化
187

被折叠的 条评论
为什么被折叠?



