尚硅谷Scala (13-14)

十三、函数式编程高级

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)
    }
  }
}

 注意事项:当要重复计算时,使用递归要考虑优化

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值