第 13 章 函数式编程高级

一、偏函数(partial function)

1、提出需求
1. 给你一个集合val list01 = List(1, 2, 3, 4, "abc") ,请完成如下要求:
	a): 将集合list中的所有数字+1,并返回一个新的集合;
	b): 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5).

模式匹配与filter-map方式解决问题示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object PartialFunTest01 {

    def main(args: Array[String]): Unit = {

        /*
        1. 给你一个集合val list01 = List(1, 2, 3, 4, "abc") ,请完成如下要求:
            a): 将集合list中的所有数字+1,并返回一个新的集合;
            b): 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5).
         */

        val list01 = List(1, 2, 3, 4, "abc")

        // 解决方式一:采用filter-map的方式处理,返回一个新的集合
        // 缺点:虽然可以解决,但是比较麻烦,需要写好几个函数
        val list02 = list01.filter(filterFun).map(mapFun)
        println("方式一list02:" + list02)

        // 解决方式二:采用match - case(模式匹配)方式处理,返回一个新的集合
        // 缺点:虽然写起来比较简单,但是不够优雅完美!
        val list03 = list01.map(matchFun)
        println("方式二list03:" + list03)

    }

    def filterFun(ele: Any): Boolean = {

        ele.isInstanceOf[Int]

    }

    def mapFun(num: Any): Int = {

        num.asInstanceOf[Int] + 1
    }

    def matchFun(ele: Any): Any = {
        ele match {
            case num: Int => num + 1
            case _ =>
        }
    }

}
===============================运行结果===============================
方式一list02:List(2, 3, 4, 5)
方式二list03:List(2, 3, 4, 5, ())
===============================运行结果===============================
2、基本介绍
1. 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择;
2. 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值
的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于怎样处理);
3. 偏函数在Scala中是一个特质PartialFunction。

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object PartialFunTest02 {

    def main(args: Array[String]): Unit = {

        /*
        使用偏函数解决如下问题:
        1. 给你一个集合val list01 = List(1, 2, 3, 4, "abc") ,请完成如下要求:
            a): 将集合list中的所有数字+1,并返回一个新的集合;
            b): 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5).
         */

        val list01 = List(1, 2, 3, 4, "abc")

        /**
          * 说明:定义一个偏函数
          *     1. PartialFunction[Any, Int]表示偏函数接收的参数类型是Any,返回类型是Int;
          *     2. isDefinedAt(x: Any)如果返回true,就会去调用apply构建构建对象实例,如果
          *        是false,则直接过滤;
          *     3. apply构造器,对传入的值+1运算,并返回(新的集合)。
          */

        val partialFun = new PartialFunction[Any, Int] {
            override def isDefinedAt(x: Any): Boolean = {
                x.isInstanceOf[Int]
            }

            override def apply(v1: Any): Int = {
                v1.asInstanceOf[Int] + 1
            }
        }

        /**
          * 说明:使用偏函数
          *     1. 如果使用偏函数,则不能使用map高阶函数,而是使用collect高阶函数;
          *     2. 偏函数的执行流程
          *         a): 遍历list01所有元素;
          *         b): 然后调用 val partialFun定义的偏函数 --> isDefinedAt(list的单个元素) --> 进行判断
          *             如果为true,再调用apply(list的单个元素)进行运算,将结果放入到新的集合,如果为false,
          *             则直接过滤。
          */

        val list02 = list01.collect(partialFun)   // List(2, 3, 4, 5)
        println("偏函数解决问题list02:" + list02)

    }

}

偏函数的小结

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

偏函数简写形式示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object PartialFunTest03 {

    def main(args: Array[String]): Unit = {

        /*
        使用偏函数简写形式解决如下问题:
        1. 给你一个集合val list01 = List(1, 2, 3, 4, "abc") ,请完成如下要求:
            a): 将集合list中的所有数字+1,并返回一个新的集合;
            b): 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5).
         */

        val list01 = List(1, 2, 3, 4, 6.8, "abc")

        // 偏函数简写方式一
        val list02 = list01.collect(partialFun)
        println("方式一list02:" + list02)   // List(2, 3, 4, 5, 13)

        // 偏函数简写方式二
        val list03 = list01.collect{
            case ele: Int => ele + 1
            case ele: Double => (ele * 2).toInt
            case ele: Float => (ele * 3).toInt
        }
        println("方式二list03:" + list03)    // List(2, 3, 4, 5, 13)
        
    }

    // 偏函数简写方式一
    def partialFun: PartialFunction[Any, Int] = {
        // 简写成case语句
        case ele: Int => ele + 1
        case ele: Double => (ele * 2).toInt
    }
    
}

二、作为参数的函数

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

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object FunArgsTest01 {

    def main(args: Array[String]): Unit = {

        // 函数作为参数传给函数使用
        val list01 = List(1, 3, 6, 12, 18, 7, 21)
        val list02 = list01.map(plus(_))   // List(7, 9, 12, 24, 36, 14, 42)
        println("list02:" + list02)

        // 在Scala中,函数也是有类型的,比如plus就是<function1>
        println("plus函数的类型:" + (plus _))   // <function1>

    }

    def plus(x: Int): Int = {
        if (x > 5) {
            x * 2
        } else {
            x + 6
        }
    }

}

上述代码小结

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

三、匿名函数

1、基本介绍
没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数。

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object AnonymouseFunTest01 {

    def main(args: Array[String]): Unit = {

        /**
          * 说明:匿名函数
          *     1. 不需要写“def 函数名”
          *     2. 不需要写返回类型,使用的是类型推导;
          *     3. “=” 变成 “=>”写法;
          *     4. 如果有多行,则使用{}包括。
          */

        val res = (num: Double) => {
            println("原有输入的值:" + num)
            (3 * num).toInt
        }

        println("res:" + res(7.8))   // 23

        /*
            练习:写一个匿名函数,返回两个整数的乘积!
         */

        val res02 = (n1: Int, n2: Int) => n1 * n2
        println("res02:" + res02(5, 9))   // 45

    }

}

四、高阶函数

1、基本介绍
能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。可使应用程序更加健壮!

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object HigherOrderFunTest01 {

    val map = Map("Jack" -> 23, "Leo" -> 19, "Tom" -> 26)

    def main(args: Array[String]): Unit = {

        val name = "Jack"
        val age = getInfo(getAge, name)
        println(name + " --> " + age)   // Jack --> 23

        /**
          * 说明:高阶函数可以返回函数类型
          *     1. minusxy是高阶函数,因为它返回匿名函数;
          *     2. 返回的匿名函数:(y: Int) => x - y
          *     3. 返回的匿名函数可以使用变量接收
          */

        // 高阶函数:minusxy
        def minusxy(x: Int) = {
            (y: Int) => x - y   // 匿名函数
        }

        val f = minusxy(5)   // 相当于 val f = (y: Int) => 5 - y
        println("f的类型:" + f)   // <function1>
        println("求值:" + f(9))  // -4

    }

    // 高阶函数基本使用
    // 能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。

    def getInfo(fun: String => Int, name: String): Int = {

        fun(name)

    }

    // getAge 接收一个String类型的参数,返回Int类型
    def getAge(name: String): Int = {
        map.getOrElse(name, 28)
    }

}

五、参数(类型)推断

1、基本介绍
	参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如:
		list=(1,2,3) list.map()   
	map中函数参数类型是可以推断的),同时也可以进行相应的简写。

参数类型推断写法说明

1. 参数类型是可以推断时,可以省略参数类型;
2. 当传入的函数,只有单个参数时,可以省去括号;
3. 如果变量只在=>右边只出现一次,可以用_来代替。

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object ParameterInferTest01 {

    def main(args: Array[String]): Unit = {

        val list01= List(1, 3, 6, 10)

        // list01每个元素加1
        println(list01.map((x: Int) => x + 1))  // List(2, 4, 7, 11)
        println(list01.map((x) => x + 1))  // List(2, 4, 7, 11)
        println(list01.map(x => x + 1))  // List(2, 4, 7, 11)
        // 最后简写
        println(list01.map(_ + 1))  // List(2, 4, 7, 11)

        // list01所有元素求和
        println(list01.reduce(sumFun))  // 20
        println(list01.reduce((n1: Int, n2: Int) => n1 + n2))  // 20
        println(list01.reduce((n1, n2) => n1 + n2))  // 20
        // 最后简写
        println(list01.reduce(_ + _))  // 20
        
    }

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

}

六、闭包(closure)

1、基本介绍
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object ClosureTest01 {

    def main(args: Array[String]): Unit = {

        // 1. 用等价理解方式改写
        // 2. 对象属性理解

        def minusxy(x: Int) = (y: Int) => x -y
        
        // fun函数就是闭包
        val fun = minusxy(20)
        println("fun(3):" + fun(3))   // 17
        println("fun(6):" + fun(6))   // 14

    }

}

上述代码小结

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

闭包的最佳实践

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

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object ClosureTest02 {

    def main(args: Array[String]): Unit = {

        val file_name01 = "java.jpg"
        val file_name02 = "scala"

        // 闭包函数
        val fun = makeSuffix(".jpg")
        val file_name_res01 = fun(file_name01)
        val file_name_res02 = fun(file_name02)

        println(file_name_res01 + " -- " + file_name_res02)  // java.jpg -- scala.jpg

    }
    
    def makeSuffix(suffix: String) = (file_name: String) => {
        if (file_name.endsWith(".jpg")) {
            file_name
        } else {
            file_name + suffix
        }
    }

}

通过上述实践代码体会闭包的好处

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

七、函数柯里化(curry)

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

示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object CurryTest01 {

    def main(args: Array[String]): Unit = {

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

        val num01 = 5
        val num02 = 7

        // 1. 使用常规的方式完成
        def mul(n1: Int, n2: Int): Int = {
            n1 * n2
        }
        println("方式1结果:" + mul(num01, num02))  // 35

        // 2. 使用闭包的方式完成
        def closureMul(n1: Int) = (n2: Int) => n1 * n2
        
        // 闭包函数:
        val cmul = closureMul(num01)
        println("方式2结果:" + cmul(num02))  // 35

        // 3. 使用函数柯里化完成
        def curryMul(n1: Int)(n2: Int) = n1 * n2
        println("方式3结果:" + curryMul(num01)(num02))   // 35

    }

}

函数柯里化最佳实践

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

上述实践示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object CurryTest02 {

    def main(args: Array[String]): Unit = {

        // 方式1:柯里化解决
        def curryEq(str01: String)(str02: String) = {
            // str01.toUpperCase().equals(str02.toUpperCase())
            str01.toUpperCase == str02.toUpperCase
        }

        val str01 = "English Java"
        val str02 = "english JAVA"

        println("是否相等:" + curryEq(str01)(str02))

        // 方式2:使用稍微高级的用法(隐式类):形式为 str.方法()
        def eq(str01: String, str02: String): Boolean = {
            str01.equals(str02)
        }

        implicit class CheckEq(str1: String) {

            def getRes(str2: String)(fun: (String, String) => Boolean): Boolean = {
                fun(str1.toLowerCase, str2.toLowerCase)
            }

        }

        println("是否相等:" + str01.getRes(str02)(eq))  // true
        // 不使用自己写的eq方法, 其中:“_.equals(_)”相当于一个匿名函数
        println("是否相等:" + str01.getRes(str02)(_.equals(_)))  // true  

    }

}

八、控制抽象

1、看一个需求
	如何实现将一段代码(从形式上看),作为参数传递给高阶函数,在高阶函数内部执行这段代码.,其使用
的形式 如breakable{} 。
	示例如下:
			var n = 10
			breakable {
				while (n <= 20) {
					n += 1
					if (n == 18) {
					  break()
					}
				}
			}
2、控制抽象基本介绍
1. 控制抽象是这样的函数,满足如下条件:
		a): 参数是函数
		b): 函数参数没有输入值也没有返回值

入门示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object AbstractControlTest01 {

    def main(args: Array[String]): Unit = {

        // 入门案例
        // myRunInThread就是一个抽象控制
        // 没有输入,也没有输出的函数(fun: () => Unit)就是抽象控制

        def myRunInThread(fun: () => Unit) = {
            new Thread{
                override def run(): Unit = {
                    fun()  // 只写了 fun: ()
                }
            }.start()
        }

        myRunInThread{
            () => println("01开始干活,5秒干完!")
                Thread.sleep(5000)
                println("01干完活了!")
        }

        // 简写形式,省略()
        def myRunInThread02(fun: => Unit): Unit = {
            new Thread{
                override def run(): Unit = {
                    fun  // 只写了 fun
                }
            }.start()
        }

        myRunInThread02 {
            println("02开始干活,5秒干完!")
            Thread.sleep(5000)
            println("02干完活了!")
        }

    }

}

进阶示例代码:

package com.lj.scala.funcationmost

/**
  * @author Administrator
  * @create 2020-03-18
  */
object AbstractControlTest02 {

    def main(args: Array[String]): Unit = {

        // scala自己的while函数
        var num = 10
        while(num > 0) {
            num -= 1
            println("while num:" + num)
        }

        // 进阶用法:实现类似while的myWhile函数
        /**
          * 说明:
          *   1. 函数名为myWhile,实现了类似while循环的效果
          *   2. condition: => Boolean是一个没有输入值,返回Boolean类型函数
          *   3. block: => Unit没有输入值,也没有返回值的抽象控制函数
          */
        def myWhile(condition: => Boolean)(block: => Unit): Unit = {

            // 类似while循环,递归
            if (!condition) {
                block   // 9 8 7 6 .....
                myWhile(condition)(block)
            }

        }

        num = 10
        myWhile(num == 0) {

            // block 代码块!
            num -= 1
            println("myWhile num:" + num)

        }

    }

}

对以前的知识回顾,加深基础知识!
学习来自:北京尚硅谷韩顺平老师—尚硅谷大数据技术之Scala
每天进步一点点,也许某一天你也会变得那么渺小!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值