• Scala函数式编程高级


    [toc]

    ## Scala函数式编程高级

    > 先看一个需求:
    >
    > 给你一个集合`val list = List(1, 2, 3, 4, "abc")` ,请完成如下要求:
    >
    > 1. 将集合list中的所有数字+1,并返回一个新的集合。
    > 2. 要求忽略掉非数字的元素,即返回的新的集合形式为 `(2, 3, 4, 5)`
    >
    > 解决方式:
    >
    > 1. filter和map返回新的集合
    > 2. 模式匹配

    ~~~~scala
    package com.atguigu.chapter13

    /**
    * @Date 2021/4/3 20:21
    * @Version 10.21
    * @Author DuanChaojie
    */
    object PartialFunDemo01 {
    def main(args: Array[String]): Unit = {
    /**
    * 给你一个集合val list = List(1, 2, 3, 4, "abc") ,请完成如下要求:
    * 将集合list中的所有数字+1,并返回一个新的集合
    * 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)
    */
    /**
    * 思路1 filter + map 方式解决
    * 虽然可以解决问题,但是麻烦.
    */
    val list = List(1, 2, 3, 4, "hello")

    /** 方案一:
    * val res1 = list.filter(filterFun)
    * println("res1 = " + res1)
    *
    * val res2 = res1.map(anyToInt)
    * println("res2 = " + res2)
    *
    * val res3 = res2.map(funAddOne)
    * println("res3 = " + res3)
    */
    val res1 = list.filter(filterFun).map(anyToInt).map(funAddOne)
    println(res1)

    /**
    * 解决方案二:使用模式匹配
    */
    val res2 = list.map(addOne)
    println(res2)
    }

    def addOne(x: Any): Any = {
    x match {
    case n: Int => n + 1
    case _ =>
    }
    }

    /**
    * @param x 输入的类型为Any类型
    * @return 输入的x为Int类型才返回true
    */
    def filterFun(x: Any): Boolean = {
    x.isInstanceOf[Int]
    }

    /**
    * @param n Any类型的参数
    * @return 返回Int
    */
    def anyToInt(n: Any): Int = {
    n.asInstanceOf[Int]
    }

    /**
    * @param n
    * @return n + 1
    */
    def funAddOne(n: Int): Int = {
    n + 1
    }

    }
    ~~~~

    ### 1. 偏函数

    > 1. 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择。
    > 2. 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于你打算怎样处理)
    > 3. 偏函数在Scala中是一个`特质PartialFunction`
    > 4. 使用偏函数解决前面的问题:

    ~~~scala
    /**
    * @Date 2021/4/3 20:36
    * @Version 10.21
    * @Author DuanChaojie
    */
    object PartialFunDemo02 {
    def main(args: Array[String]): Unit = {
    //使用偏函数解决
    val list = List(1, 2, 3, 4, "hello")


    /**
    * 定义一个偏函数
    * 1. PartialFunction[Any,Int] 表示偏函数接收的参数类型是Any,返回类型是Int
    * 2. isDefinedAt(x: Any) 如果返回true ,就会去调用 apply 构建对象实例,如果是false,过滤
    * 3. apply 构造器 ,对传入的值 + 1,并返回(新的集合)
    */
    val partialFun = new PartialFunction[Any, Int] { // PartialFunction是一个特质
    override def isDefinedAt(x: Any): Boolean = {
    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 res = list.collect(partialFun)
    // List(2, 3, 4, 5)
    println(res)

    }
    }
    ~~~

    > 偏函数小结:
    >
    > 1. 使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)
    > 2. PartialFunction 是个特质(看源码)
    > 3. 构建偏函数时,参数形式 [Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数
    > 4. 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行isDefinedAt()如果为true ,就会执行 apply, 构建一个新的Int 对象返回。
    > 5. 执行isDefinedAt() 为false 就过滤掉这个元素,即不构建新的Int对象。
    > 6. map函数不支持偏函数,因为map底层的机制就是所有循环遍历,无法过滤处理原来集合的元素
    > 7. `collect函数支持偏函数。`

    #### 偏函数简化形式

    `声明偏函数,需要重写特质中的方法,有的时候会略显麻烦,而Scala其实提供了简单的方法`

    ~~~~scala

    /**
    * @Date 2021/4/3 20:47
    * @Version 10.21
    * @Author DuanChaojie
    */
    object PartialFunDemo03 {
    def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4, "hello")

    /**
    * 第一种简写方式:使用case
    * @return
    */
    def partialFun1: PartialFunction[Any, Int] = {
    case i: Int => i + 1
    }

    val res1 = list.collect(partialFun1)
    //res1 = List(2, 3, 4, 5)
    println("res1 = " + res1)

    /**
    * 第二种简写方式:
    */
    val res2 = list.collect {
    case i: Int => i + 1
    }
    // res2 = List(2, 3, 4, 5)
    println("res2 = " + res2)
    }
    }
    ~~~~

    #### 作为参数的函数

    函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:`function1`,即:`(参数类型) => 返回类型`

    ~~~~scala
    /**
    * @Date 2021/4/3 20:55
    * @Version 10.21
    * @Author DuanChaojie
    */
    object FunParameterDemo01 {
    def main(args: Array[String]): Unit = {
    def plus(x: Int) = 3 + x
    //val res = Array(3,6,9).map(plus(_))
    val res = Array(3,6,9).map(plus)

    // mkString(seq:String)方法是将原字符串使用特定的字符串seq分割。
    // 6,9,12
    println(res.mkString(","))

    /**
    * 在scala中,函数也是有类型,比如plus就是<function1>
    */
    println(plus _)
    }
    }
    ~~~~

    > 小结:
    >
    > 1. `map(plus(_))` 中的`plus(_)` 就是将plus这个函数当做一个参数传给了map,`_这里代表从集合中遍历出来的一个元素。`
    > 2. `plus(_)` 这里也可以写成 plus 表示对 Array(1,2,3,4) 遍历,将每次遍历的元素传给plus的 x
    > 3. 进行 3 + x 运算后,返回新的Int ,并加入到新的集合 res中
    > 4. `def map[B, That](f: A => B)` 的声明中的 `f: A => B` 一个函数

    ### 2. 匿名函数

    > 没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数。

    ~~~~scala
    /**
    * @Date 2021/4/3 21:02
    * @Version 10.21
    * @Author DuanChaojie
    */
    object AnonymousFunDemo01 {
    def main(args: Array[String]): Unit = {
    /**
    * 对匿名函数的说明
    * 1. 不需要写def函数名
    * 2. 不需要写返回类型,使用类型推导
    * 3. =变成=>
    * 4. 如果有多行,则使用{} 包括
    */
    val triple = (x: Double) => {
    println("x = " + x)
    x * x
    }
    // triple(3) = 9.0
    println("triple(3) = " + triple(3))
    }
    }
    ~~~~

    > 课堂案例:请编写一个匿名函数,可以返回2个整数的和,并输出该匿名函数的类型

    ~~~~scala
    /**
    * @Date 2021/4/3 21:41
    * @Version 10.21
    * @Author DuanChaojie
    */
    object AnonymousFunDemo02 {
    def main(args: Array[String]): Unit = {
    val sum = (n1: Int, n2: Int) => {
    n1 + n2
    }

    // 36
    println(sum(21, 15))
    // sum函数的类型是:<function2>
    println("sum函数的类型是:" + sum)
    }
    }
    ~~~~

    ### 3. 高阶函数

    > ==能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)==。可使应用程序更加健壮。

    ~~~scala
    /**
    * @Date 2021/4/3 21:44
    * @Version 10.21
    * @Author DuanChaojie
    */
    object HigherOrderFunDemo01 {
    def main(args: Array[String]): Unit = {
    val res = hfun(fun1, fun2, 3.5)
    println("res = " + res)
    }

    /**
    * 高阶函数
    * @param f1
    * @param f2
    * @param n
    * @return 将n进行以下处理返回:(n+n).toInt
    */
    def hfun(f1: Double => Double, f2: Double => Int, n: Double) = {
    f2(f1(n))
    }


    /** 普通函数fun1
    * @param x 输入Double类型的值
    * @return x + x
    */
    def fun1(x: Double): Double = {
    x + x
    }

    /** 普通函数fun2
    * @param x 输入Double类型的值
    * @return 返回对应的Int
    */
    def fun2(x: Double): Int = {
    x.toInt
    }
    }
    ~~~

    > 高阶函数可以返回函数类型

    ~~~~scala
    /**
    * @Date 2021/4/3 21:52
    * @Version 10.21
    * @Author DuanChaojie
    */
    object HigherOrderFunDemo02 {
    def main(args: Array[String]): Unit = {
    /**
    * 这里返回的fun1就是minusxy里面的匿名函数:(y: Int) => 3 + y
    * 等价于 def fun1 = (y: Int) => 3 + y
    * 所以fun1(6)的结果为9
    * 也可以一步到位:minusxy(3)(6)
    */
    val fun1 = minusxy(3)
    // fun1 = <function1>
    println("fun1 = " + fun1)
    val res = fun1(6)
    // res = 9
    println("res = " + res)

    // minusxy(3)(6) = 9
    println("minusxy(3)(6) = " + minusxy(3)(6))

    }

    def minusxy(x: Int) = {
    // 匿名函数
    (y: Int) => x + y
    }
    }
    ~~~~

    ### 4. 类型推断

    > 参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如`list=(1,2,3) list.map()` map中函数参数类型是可以推断的),同时也可以进行相应的简写。
    >
    > 参数类型推断写法说明:
    >
    > 1. `参数类型是可以推断时,可以省略参数类型`
    > 2. `当传入的函数,只有单个参数时,可以省去括号`
    > 3. `如果变量只在=>右边只出现一次,可以用_来代替`

    ~~~~scala
    /**
    * @Date 2021/4/3 22:04
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ParameterInferDemo {
    /**
    * 参数类型是可以推断时,可以省略参数类型
    * 当传入的函数,只有单个参数时,可以省去括号
    * 如果变量只在=>右边只出现一次,可以用_来代替
    */
    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)


    println(list.reduce(fun1)) // 10
    println(list.reduce((n1: Int, n2: Int) => n1 + n2)) //10
    println(list.reduce((n1, n2) => n1 + n2)) //10
    println(list.reduce(_ + _)) //10
    }

    def fun1(n1: Int, n2: Int): Int = {
    n1 + n2
    }
    }
    ~~~~

    > 1. `map是一个高阶函数,因此也可以直接传入一个匿名函数,完成map`
    > 2. `当遍历list时,参数类型是可以推断出来的,可以省略数据类型Int println(list.map((x)=>x + 1))`
    > 3. `当传入的函数,只有单个参数时,可以省去括号 println(list.map(x=>x + 1))`
    > 4. `如果变量只在=>右边只出现一次,可以用_来代替 println(list.map(_ + 1))`

    ### 5. 闭包

    闭包就是==一个函数==和==与其相关的引用环境==组合的一个==整体==(实体)。

    通过一个例子了解闭包:

    ~~~~scala
    object Demo {
    def main(args: Array[String]): Unit = {
    val fun1 = minusxy(3)
    val res1 = fun1(6)
    val res2 = fun1(9)
    }

    def minusxy(x: Int) = {
    // 匿名函数
    (y: Int) => x + y
    }
    }
    ~~~~

    > 1. `(y: Int) => x + y`返回的是一个匿名函数 ,因为该函数引用到到函数外的 x,那么该函数和x整体形成一个闭包如:`这里 val fun1 = minusxy(3) 的fun1函数就是闭包`
    > 2. 你可以这样理解,返回函数是一个对象,而x就是该对象的一个字段,他们共同形成一个闭包
    > 3. 当多次调用fun1时(可以理解多次调用闭包),发现使用的是同一个x, 所以x不变。
    > 4. 在使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为他们会组合成一个整体(实体),形成一个闭包。

    #### 闭包的最佳实践

    > 请编写一个程序,具体要求如下:
    >
    > 1. 编写一个函数 `makeSuffix(suffix: String)` 可以接收一个文件后缀名(比如.png),并返回一个闭包
    > 2. 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.png) ,则返回 文件名.png, 如果已经有`.png`后缀,则返回原文件名。
    > 3. 要求使用闭包的方式完成
    > 4. `String.endsWith(xx)`

    ~~~~scala
    /**
    * @Date 2021/4/3 22:13
    * @Version 10.21
    * @Author DuanChaojie
    */
    object ClosureDemo01 {
    def main(args: Array[String]): Unit = {
    val fun = makeSuffix(".png")

    // dog.png
    println(fun("dog.png"))
    // cat.png
    println(fun("cat"))
    }

    /**
    * @param suffix 传入
    * @return
    */
    def makeSuffix(suffix: String) = {
    // 返回一个匿名函数,会使用到suffix
    (filename: String) => {
    if (filename.endsWith(suffix)) {
    filename
    } else {
    filename + suffix
    }
    }
    }
    }
    ~~~~

    > 体会闭包的好处:
    >
    > 1. 返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量 组合成一个闭包,因为返回的函数引用到suffix这个变量
    > 2. 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .png ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把。

    ### 6. 函数柯里化(curry)

    > 1. `函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化。`
    > 2. 柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作。
    > 3. 不用设立柯里化存在的意义这样的命题。柯里化就是以函数为主体这种思想发展的必然产生的结果。(即:柯里化是面向函数思想的必然产生结果)

    #### 函数柯里化快速入门

    > 编写一个函数,接收两个整数,可以返回两个数的乘积,要求:
    >
    > 1. 使用常规的方式完成
    > 2. 使用闭包的方式完成
    > 3. 使用函数柯里化完成

    ```scala
    /**
    * @Date 2021/4/3 22:20
    * @Version 10.21
    * @Author DuanChaojie
    */
    object CurryDemo01 {
    def main(args: Array[String]): Unit = {

    // 使用常规的方式完成
    def curry1(x: Int, y: Int) = x * y
    println(curry1(10, 10))

    // 使用闭包的方式完成
    def curry2(x: Int) = (y: Int) => x * y
    println(curry2(10)(10))

    // 使用函数柯里化完成
    def curry3(x: Int)(y: Int) = x * y
    println(curry3(10)(10))
    }
    }
    ```

    #### 函数柯里化最佳实践

    > 比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
    >
    > 1. 全部转大写(或小写)
    > 2. 比较是否相等
    >
    > 针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)

    ```scala
    /**
    * @Date 2021/4/3 22:23
    * @Version 10.21
    * @Author DuanChaojie
    */
    object CurryDemo02 {
    def main(args: Array[String]): Unit = {
    val str = "ddaimm"
    // false
    println(str.compareStr("HELLO")(fun))

    // true
    println(str.compareStr("ddaimm")(_.equals(_)))
    }

    /**
    * 可以接收两个字符串,比较是否相等
    */
    def fun(str1: String, str2: String): Boolean = {
    str1.equals(str2)
    }

    /**
    * 隐式类
    */
    implicit class compare(str1: String) {
    /**
    * 体现了将比较字符串的事情,分解成两个任务完成
    * 1. compareStr 转换大小写
    * 2. fun函数完成比较任务
    */
    def compareStr(str2: String)(fun: (String, String) => Boolean): Boolean = {
    fun(str2.toUpperCase(), str1.toUpperCase())
    }
    }

    }
    ```

    ### 7. 控制抽象

    > `如何实现将一段代码(从形式上看),作为参数传递给高阶函数,在高阶函数内部执行这段代码.,其使用的形式如 breakable{} 。`

    ~~~~scala
    var n = 10
    breakable {
    while (n <= 20) {
    n += 1
    if (n == 18) {
    break()
    }
    }
    }
    ~~~~

    > 控制抽象是这样的函数,满足如下条件
    >
    > 1. `参数是函数`
    > 2. `函数参数没有输入值也没有返回值`

    ~~~~scala
    /**
    * @Date 2021/4/3 22:37
    * @Version 10.21
    * @Author DuanChaojie
    */
    object AbstractControlDemo01 {
    def main(args: Array[String]): Unit = {
    /**
    * myRunInThread 就是一个抽象控制
    * 是没有输入, 也没有输出的函数 f1: () => Unit
    */
    def myRunInThread(f1: () => Unit) = {
    new Thread {
    override def run(): Unit = {
    f1() //只写了 f1
    }
    }.start()
    }

    myRunInThread { () => {
    println("mm干活咯!5秒完成...")
    Thread.sleep(5000)
    println("mm干完咯!")
    }
    }

    //简写形式
    def myRunInThread2(f1: => Unit) = {
    new Thread {
    override def run(): Unit = {
    f1 //只写了 f1
    }
    }.start()
    }

    // 对于没有输入,也没有返回值函数,可以简写成如下形式
    myRunInThread2 {
    println("dd干活咯!5秒完成...")
    Thread.sleep(5000)
    println("dd干完咯!")
    }

    }
    }
    ~~~~

    > `进阶用法:实现类似while的until函数`

    ~~~~scala
    /**
    * @Date 2021/4/3 22:44
    * @Version 10.21
    * @Author DuanChaojie
    */
    object AbstractControlDemo02 {
    def main(args: Array[String]): Unit = {

    var x = 10

    until(x == 0) {
    x -= 1
    println("x=" + x)
    }

    }

    /** 说明
    * 1 函数名为 until , 实现了类似 while循环的效果
    * 2. condition: => Boolean 是后一个没有输入值,返回Boolean类型函数
    * 3. block: => Unit 没有输入值,也没有返回值的韩
    */
    def until(condition: => Boolean)(block: => Unit): Unit = {
    //类似while循环,递归
    if (!condition) {
    block // x= 9 ,x = 8 x =7 ....
    until(condition)(block)
    }

    }
    }
    ~~~~

    ## ☆

  • 相关阅读:
    Qt5对付中文真好用
    Qt5下的常见问题————C1083
    macbook pro retina 编程字体推荐
    boost::xml————又一次失败的尝试
    boost::xml——基本操作以及中文乱码解决方案 (续)
    C++单元测试2
    C++单元测试
    生成dll文件的示例
    咋一看你能看明白吗?
    boost::function实践——来自《Beyond the C++ Standard Library ( An Introduction to Boost )》
  • 原文地址:https://www.cnblogs.com/huaobin/p/15782721.html
Copyright © 2020-2023  润新知