scala(六) 高阶函数

2021-06-23  本文已影响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)
  }

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

上一篇下一篇

猜你喜欢

热点阅读