美文网首页
scala(六) 高阶函数

scala(六) 高阶函数

作者: 万事万物 | 来源:发表于2021-06-23 01:04 被阅读0次

    介绍

    高阶函数:以函数作为参数或返回值的方法(函数)称为高阶函数。我的理解是高阶函数是一种思想,它的作用能让我们的程序更加灵活。
    思考:如果让你实现一个计算器,功能不多,只有+-*/ 四个功能。
    方式一:普通方式
    定义四个方法(函数)

      def main(args:Array[String]):Unit={
    
        // 加法
        def add(x:Int,y:Int)=x+y
    
        // 减法
        def minus(x:Int,y:Int)=x-y
    
        // 乘法
        def multiply(x:Int,y:Int)=x-y
    
        // 除法
        def division(x:Int,y:Int)=x/y
        
      }
    

    编写一个计算器方法,负责统一调用

        def calculator(x:Int,y:Int,options:String):Int={
          if(options=="+"){
            add(x,y)
          }else if(options=="-"){
            minus(x,y)
          }else if(options=="*"){
            multiply(x,y)
          }else if(options=="/"){
            division(x,y)
          }else{
            println("不支持该功能")
            -1
          }
        }
    

    这种方式简单,但是限制太多了,要是在加一个功能,如取余的方法(函数),那么就需要重新定义一个取余的方法(函数)。要是再来1020个功能呢?难道需要定义这么多方法(函数)?

    方式二:高阶函数一

    当你看到高阶函数的时候,应该对函数有一定的了解。先不说scala是一个完全面向对象的语言,就函数而言,它本身就是一个对象。FuncationN 对象,N 表示0-22 个数字(不太了解,可能会有点绕)。

    既然函数是个对象,那么肯定可以作为参数用来传递。
    于是我们可以定义一个计算器函数: 除了传递正常的数值外,还需要把你具体的实现传递给我。

        /**
         * 计算器
         * @param x 参数 1
         * @param y 参数 2
         * @param func 函数对象
         * @return
         */
        def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
          // 具体的功能有函数来实现
          func(x,y)
        }
    

    完整代码

      def main(args:Array[String]):Unit={
    
        // 加法
        def add(x:Int,y:Int)=x+y
    
        // 减法
        def minus(x:Int,y:Int)=x-y
    
    
        // 乘法
        def multiply(x:Int,y:Int)=x-y
    
        // 除法
        def division(x:Int,y:Int)=x/y
    
    
        /**
         * 计算器
         * @param x 参数 1
         * @param y 参数 2
         * @param func 函数对象
         * @return
         */
        def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
          // 具体的功能有函数来实现
          func(x,y)
        }
        // 除法
        val result=calculator(12,4,division) // 3
        println(result)
      }
    

    比如若现在需要使用加法;我们只需要更改 funcadd 即可。

        val result=calculator(12,4,add)
    
        println(result) // 16
    

    这种方式:更加灵活,思想更加活跃,最开始接触高阶函数的时候也是一脸懵逼,依样画葫芦的写,达不到活学活用,觉得特难。至于什么时候想明白的我也不知道,接触多了就自然而然就明白了。

    回到正题,你是否还有疑问?即便是将函数以参数的形式执行,不是还得先把函数定义好吗?10个20个功能还不是得继续加代码,写函数。

    哈哈确实是,但是这种思想,我们应该要明白,否则看到别人的写的函数以这样的形式都不知道啥意思就奇怪了。


    对高阶函数有了一定了解后,就来玩玩scala中的高阶函数;看看他你能玩出什么花来。
    还是以这个为例

        // 加法
        def add(x:Int,y:Int)=x+y
    
        // 减法
        def minus(x:Int,y:Int)=x-y
    
        // 乘法
        def multiply(x:Int,y:Int)=x-y
    
        // 除法
        def division(x:Int,y:Int)=x/y
    

    你觉得 写这种(如上)太麻烦了,就优化一下;上面是以方法的形式,带有def 关键字。这里改成函数的形式。看起来更加像一个参数,有木有。
    函数和方法其实是一样的,只不过表达形式不同,没必要太纠结。

      def main(args:Array[String]):Unit={
    
        // 加法
        val add=(x:Int,y:Int)=>x+y
        // 减法
        val minus=(x:Int,y:Int)=>x-y
    
        // 乘法
        val multiply=(x:Int,y:Int)=>x*y
    
        // 除法
        val division=(x:Int,y:Int)=>x/y
    
    
        /**
         * 计算器
         * @param x 参数 1
         * @param y 参数 2
         * @param func 函数对象
         * @return
         */
        def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
          // 具体的功能有函数来实现
          func(x,y)
        }
    
    
        val result=calculator(12,4,multiply)
        println(result) // 48
      }
    

    觉得没啥感觉?那我就删了,

      def main(args:Array[String]):Unit={
    
    
        /**
         * 计算器
         * @param x 参数 1
         * @param y 参数 2
         * @param func 函数对象
         * @return
         */
        def calculator(x:Int,y:Int,func:(Int,Int)=>Int):Int={
          // 具体的功能有函数来实现
          func(x,y)
        }
    
    
        val result=calculator(12,4,_+_)  
    
        println(result) //16
    
      }
    
    

    换成除法:

        val result=calculator(12,4,_/_)  
        println(result) //3
    

    这种方式,够离谱了吧,改到最后都不认识了。
    sacla 中有很多地方都会用到下划线_;其用途很强大的,就说说这里的_的作用。在这里,它可以作为一个参数,拿_/_为例,第一个_就代表参数x的值,第二个_就代表参数y的值。

    浅谈 Scala 中下划线的用途

    高阶函数简化(调用时简化)

    1. 标准写入(以上面的案例说明)
        // 定义函数
        val add=(x:Int,y:Int)=>x+y
        // 调用
        val result=calculator(12,4,add)
        // 输出结果
        println(result)  // 16
    
    1. 直接传递函数值
        // 调用
        val result=calculator(12,4,(x:Int,y:Int)=>x+y)
        // 输出结果
        println(result)
    
    1. 参数类型一致,类型可以省略
      (x:Int,y:Int)=>x+y 与 func:(Int,Int) 是一致的。
        // 调用
        val result=calculator(12,4,(x,y)=>x+y)
        // 输出结果
        println(result)
    
    1. 如果函数的参数在函数体中,只使用过一次,可以使用_ 代替。
        // 调用
        val result=calculator(12,4,_+_)
        // 输出结果
        println(result)
    
    1. 如果 函数参数只有一个,小括号可以省略
     def sayHello(str:String,func:(String)=>String): String ={
          str
     }
     println(sayHello("hello",s=>s)) // hello
    

    '_'的限制场景

    当然使用 _ 是有限制的,有些场景不可使用。

    1. 如果函数参数的使用顺序与参数定义的顺序不一样,此时不可用使用下划线代替。
      如下: 这种情况就就不能使用下划线代替,会再次数据错误。
    val result=calculator(12,4,(x,y)=>y-x)
    
    1. 如果函数体中有(),函数的参数在函数中小括号中以表达式形式存在,此时不能用小括号代替。
    val result=calculator(12,4,(x,y)=>(x+1)*y)
    
    1. 如果函数只有一个参数,并且在函数体中对参数没有做任何操作就直接返回的时候,不能使用_ 代替。
        def sayHello(str:String,func:(String)=>String): String ={
          str
        }
    
        // 普通方式
        println("普通方式:",sayHello("hello",(s)=>s))
        // 下划线的方式:
        println("下划线的方式:",sayHello("hello",_))
    

    输出:

    (普通方式:,hello)
    (下划线的方式:,Demo02$$$Lambda$7/932607259@67b64c45)
    

    思考:为什么输出的是 Demo02$$$Lambda$7/932607259@67b64c45?当如果是第三种情况是,_ 将不再是hello;而是 该函数的引用
    谁的引用?答案就是 sayHello 的引用,也就是当前函数的引用。

    案例实战

    1. **对数组中每个元素按照指定规则进行操作,操作之后返回结果结果
      数据: Array[String]("hello","spark","hadoop","flink")
      规则 [可变]: 通过该规则可以获取到 字符长度首写字母转换大小写
      获取每个元素的长度
      案例一:获取元素长度
      def main(args: Array[String]): Unit = {
        val arr=Array[String]("hello","spark","hadoop","flink")
        println(map(arr).toList)
      }
    
      /**
       * 获取元素长度
       * @param array
       */
      def map(array: Array[String]): Array[AnyVal] ={
        for (e<- array)yield e.length
      }
    
    List(5, 5, 6, 5)
    

    该规则是可变的,上面的案例是获取长度,要是获取其他信息又怎么获取呢?学习完高阶函数之后,我们自然可以想到将一个函数作为参数,将规则定义到函数中,至于什么规则,不用操心,传进来是什么就是什么就可以了。
    代码优化:优化之后,将规则作为参数传进来。

      def main(args: Array[String]): Unit = {
    
        val arr=Array[String]("hello","spark","hadoop","flink")
        println(map(arr,(s)=>{s.length}).toList)
    
      }
    
      def map(array: Array[String],func:String=>Any): Array[Any] ={
        for (e<- array)yield func(e)
      }
    
    List(5, 5, 6, 5)
    

    咦?感觉没什么?这次需求,获取列表中的首字母

      def main(args: Array[String]): Unit = {
    
        val arr=Array[String]("hello","spark","hadoop","flink")
        println(map(arr,(s)=>{s.charAt(0)}).toList)
    
      }
      def map(array: Array[String],func:String=>Any): Array[Any] ={
        for (e<- array)yield func(e)
      }
    
    List(h, s, h, f)
    

    也可以这样,将字符串全部大写

      def main(args: Array[String]): Unit = {
    
        val arr=Array[String]("hello","spark","hadoop","flink")
        println(map(arr,(s)=>{s.toUpperCase()}).toList)
      }
      def map(array: Array[String],func:String=>Any): Array[Any] ={
        for (e<- array)yield func(e)
      }
    
    List(HELLO, SPARK, HADOOP, FLINK)
    

    知道为什么需要返回 Any 吗?其主要原因是不知道规则是什么。比如:第一次是长度 类型肯定是Int,第一次是首字母肯定的是Char,第三次是转转大写,是一个String。就是因为不确定,所以需要指定为Any

    接下来就是代码优化:

    1. 由于参数只有一个,所以可以不用带()
    println(map(arr,s=>{s.toUpperCase()}).toList)
    
    1. 块中只有语句代码所以可以不用指定{}
     println(map(arr,s=>s.toUpperCase()).toList)
    
    1. 当然方法没参数,也可以不用带()
    println(map(arr,s=>s.toUpperCase).toList)
    
    1. 参数只有一个且没有重复使用的地方,所以可以使用_代替
     println(map(arr,_.toUpperCase).toList)
    

    运行结果:

    List(HELLO, SPARK, HADOOP, FLINK)
    

    1. 对数组中的数据按照指定规则过滤
      数据: Array[Int](1,4,2,7,9,10)
      规则: [可变] 通过此规则,可以获取,奇偶数3的倍数 等。
      案例一:获取所有的奇数
      def main(args: Array[String]): Unit = {
        val arr=Array[Int](1,4,2,7,9,10)
    
        println(filter(arr).toList)
      }
    
      /**
       * 过滤
       * @param arr
       * @return
       */
      def filter(arr:Array[Int]):Array[Int]={
        for (i <- arr if i % 2 != 0) yield i
      }
    
    List(1, 7, 9)
    

    规则是可以的,通过观察,发现 if i % 2 != 0 就是我们想要的规则;于是使用高阶函数进行改进。

    需要键元素传给函数,然后在函数中判断是否是需要的元素;所以传入的参数是Int 返回的是Boolean

    func:Int=>Boolean
    
      def main(args: Array[String]): Unit = {
        val arr=Array[Int](1,4,2,7,9,10)
    
        println(filter(arr,(i)=>{i%2!=0}).toList)
      }
    
      /**
       * 过滤
       * @param arr
       * @return
       */
      def filter(arr:Array[Int],func:Int=>Boolean):Array[Int]={
        for (i <- arr if func(i)) yield i
      }
    
    List(1, 7, 9)
    

    案例二:获取集合中所有的偶数

      def main(args: Array[String]): Unit = {
        val arr=Array[Int](1,4,2,7,9,10)
    
        println(filter(arr,i=>i%2==0).toList)
      }
    
      /**
       * 过滤
       * @param arr
       * @return
       */
      def filter(arr:Array[Int],func:Int=>Boolean):Array[Int]={
        for (i <- arr if func(i)) yield i
      }
    
    List(4, 2, 10)
    

    案例三:获取结合中是3的倍数

      def main(args: Array[String]): Unit = {
        val arr=Array[Int](1,4,2,7,9,10)
    
        println(filter(arr,_%3==0).toList)
      }
    
      /**
       * 过滤
       * @param arr
       * @return
       */
      def filter(arr:Array[Int],func:Int=>Boolean):Array[Int]={
        for (i <- arr if func(i)) yield i
      }
    
    
    List(9)
    
    1. 对数组中元素按照指定规则分组
      数据: Array[String]("zhangsan man beijing","lisi woman shenzhen","zhaoliu man shenzhen")
      规则: 按照规则进行分组
      通过上面两个案例的套路;应该知道该如何编写程序,按照规则进行分组,比如按照性别地区等。

    案例一:根据性别分组

     def main(args: Array[String]): Unit = {
        val arr=Array[String]("zhangsan man beijing","lisi woman shenzhen","zhaoliu man shenzhen")
    
        //根据 性别分组
        println(group(arr,s=>{s.split(" ")(1)}))
      }
    
      /**
       * 分组
       * @param arr
       * @param func
       * @return
       */
      def group(arr:Array[String],func:String=>String):util.HashMap[String,util.ArrayList[String]]={
        // 用于存放 分组数据
        val map=new util.HashMap[String,util.ArrayList[String]]()
        // 分组;具体按照什么来分组,有func 指定。
        // 只需要将 每个元素传给 func 即可。
    
        var list:util.ArrayList[String]=null
        for(e <-arr ;key=func(e)){
          // 校验key是否存在,
          if(map.containsKey(key)){
            list=map.get(key)
          }else{
            // 若key不存在,需要创建 集合
            list=new util.ArrayList[String]
          }
          list.add(e)
          // 装回map中
          map.put(key,list);
        }
        map
      }
    
    {woman=[lisi woman shenzhen], man=[zhangsan man beijing, zhaoliu man shenzhen]}
    

    案例一:根据地区分组

    def main(args: Array[String]): Unit = {
        val arr=Array[String]("zhangsan man beijing","lisi woman shenzhen","zhaoliu man shenzhen")
    
        //根据 地区分组
        println(group(arr,s=>{s.split(" ")(2)}))
      }
    
      /**
       * 分组
       * @param arr
       * @param func
       * @return
       */
      def group(arr:Array[String],func:String=>String):util.HashMap[String,util.ArrayList[String]]={
        // 用于存放 分组数据
        val map=new util.HashMap[String,util.ArrayList[String]]()
        // 分组;具体按照什么来分组,有func 指定。
        // 只需要将 每个元素传给 func 即可。
    
        var list:util.ArrayList[String]=null
        for(e <-arr ;key=func(e)){
          // 校验key是否存在,
          if(map.containsKey(key)){
            list=map.get(key)
          }else{
            // 若key不存在,需要创建 集合
            list=new util.ArrayList[String]
          }
          list.add(e)
          // 装回map中
          map.put(key,list);
        }
        map
      }
    
    {shenzhen=[lisi woman shenzhen, zhaoliu man shenzhen], beijing=[zhangsan man beijing]}
    

    代码简写:

        //根据 性别分组
        println(group(arr,_.split(" ")(2)))
    
    1. 对数组中的所有元素按照指定规则聚合
      数据: Array[Double](1,4,2,7,9,10)
      规则: 求总和,求乘积,求差等。

    案例一:求出集合中的总和

      def main(args: Array[String]): Unit = {
        val arr=Array[Double](1,4,2,7,9,10)
        println(reduce(arr,(x,y)=>{x+y}))
      }
    
      def reduce(array: Array[Double],func:(Double,Double)=>Double): Double ={
        var tmp=array(0)
        for(i <- 1 until array.length){
          tmp=func(tmp,array(i))
        }
        tmp
      }
    
    33.0
    

    案例二:求出集合中的积

      def main(args: Array[String]): Unit = {
        val arr=Array[Double](1,4,2,7,9,10)
        println(reduce(arr,(x,y)=>x*y))
    
      }
    
      def reduce(array: Array[Double],func:(Double,Double)=>Double): Double ={
    
        var tmp=array(0)
        for(i <- 1 until array.length){
          tmp=func(tmp,array(i))
        }
        tmp
      }
    
    5040.0
    

    案例二:求出集合中的差

      def main(args: Array[String]): Unit = {
        val arr=Array[Double](1,4,2,7,9,10)
        println(reduce(arr,_-_))
    
      }
    
      def reduce(array: Array[Double],func:(Double,Double)=>Double): Double ={
        var tmp=array(0)
        for(i <- 1 until array.length){
          tmp=func(tmp,array(i))
        }
        tmp
      }
    
    -31.0
    
    1. 根据指定规则获取数组中的最大值
      数据: Array[String]("zhangsan 20 3000","lisi 18 4500","zhaoliu 33 3500")
      规则: 根据不同的规则,求出最大值,如按照,年纪,薪资等。

    案例一:按照获取年龄最大的用户

    def main(args: Array[String]): Unit = {
        val arr=Array[String]("zhangsan 20 3000","lisi 18 4500","zhaoliu 33 3500")
        // 获取年龄最大的用户信息
        println(max(arr,s=>s.split(" ")(1).toInt))
    
      }
    
    
      /**
       * 获取最大值
       * @param array
       * @param func
       * @return
       */
      def max(array: Array[String],func:String=>Int):String={
    
        // 用于记录最大值元素的下标。
        var index=0
    
        for(i <- 1 until array.length){
          // 进行比较,记录最大值的元素下标
          if(func(array(index)) < func(array(i))){
            index=i
          }
        }
        array(index)
      }
    

    案例二:获取薪资最高的用户信息

      def main(args: Array[String]): Unit = {
        val arr=Array[String]("zhangsan 20 3000","lisi 18 4500","zhaoliu 33 3500")
        // 获取年龄最大的用户信息
        println(max(arr,_.split(" ")(2).toInt))
    
      }
    
    
      /**
       * 获取最大值
       * @param array
       * @param func
       * @return
       */
      def max(array: Array[String],func:String=>Int):String={
    
        // 用于记录最大值元素的下标。
        var index=0
    
        for(i <- 1 until array.length){
          // 进行比较,记录最大值的元素下标
          if(func(array(index)) < func(array(i))){
            index=i
          }
        }
    
        array(index)
      }
    
    lisi 18 4500
    

    代码优化:

      def max(array: Array[String],func:String=>Int):String={
    
        // 用于记录最大值元素的下标。
        var index=0
        for(i <- 1 until array.length;tmp=func(array(i)) if func(array(index))<tmp) index=i
        array(index)
      }
    

    之后的很多都没做说明,累了,脑袋优点晕,以后再说吧。

    相关文章

      网友评论

          本文标题:scala(六) 高阶函数

          本文链接:https://www.haomeiwen.com/subject/fbhdyltx.html