Scala学习总结(3)——函数式编程基础

3 函数式编程基础


在纯函数式编程语言里,变量就像数学语言里的代数符合,一经确定就不能改变。正是由于这种不可变性,使得函数和普通的值之间具有对等关系。这样函数就跟普通的值一样成为了“头等公民”,可以像任何其他数据类型一样被传递和操作。

scala不是完全的函数式编程语言,它在架构的上层提倡采用面向对象编程,而在对数据和各种底层操作作中使用函数式编程,对变量的定义既可以使用val,也可以使用var,所以它并不要求变量不可变。但在实际操作中,scala建议多用val,少用var,这样可以降低出错的概率。

函数的定义与使用

函数既然与普通的值对等,那么它也应该拥有类型的区别。

  • 类型

    类型应该明确函数的返回值类型,以及传入参数的类型

  • 值是一个函数的具体实现

比如:

scala> val counter:(Int) => Int = {value => value + 1}
val counter: Int => Int = $Lambda$1089/230944166@1372696b

scala> counter(5)
val res8: Int = 6

上面定义的counter是一个函数,第2行包括了它的传入参数类型以及返回值类型定义,即 (Int) => Int

符号=>的左边是该方法的传入参数类型,右边是方法的返回值类型。

等号"=" 后面大括号中的内容就是方法体。

当函数需要定义多个参数列表时:

scala> def mult(factor:Int)(x:Int) = x*factor
def mult(factor: Int)(x: Int): Int

scala> mult(90)(5)
val res9: Int = 450

高阶函数

当一个函数包含其它函数作为参数或作为返回值时,该函数称为高阶函数,可以说它是操作其它函数的函数。

scala> def sum(f:Int => Int, a:Int, b:Int):Int = { if(a>b) 0 else f(a)+sum(f,a+1,b)}
def sum(f: Int => Int, a: Int, b: Int): Int

scala> sum(x=>x*x, 1, 5)
val res10: Int = 55

上面函数的计算过程:

初始时 a = 1, b = 5

sum(x=>x*x, 1, 5)

= f(1) + sum(f, 2, 5) = 1 + f(2) +sum(f, 3, 5) = 1 + 4 + f(3) + sum(4,5)

= 1 + 4 +9 + f(4) + sum (5,5) = 1 + 4 + 9 + 16 + f(5) + sum(6,5)

= 1+ 4 + 9 + 16 + 25 + 0 = 55

闭包

有些函数的执行只依赖于传入参数的值,与调用函数的上下文无关。当函数执行时依赖于声明于函数外部的变量时,则称这个函数为闭包

偏应用函数和Curry化

  1. 偏应用函数

    一个函数在特殊应用场景下参数可能会多次取相同的值,将这个函数的部分参数整合为一个参数,传入一个新的函数中,这就称为偏应用函数。

    scala> def sum(a:Int, b:Int, c:Int):Int = {a+b+c}
    def sum(a: Int, b: Int, c: Int): Int
    
    scala> sum(1,2,3)
    val res21: Int = 6
    
    scala> sum(1,2,4)
    val res22: Int = 7
    
    scala> val a = sum(1,2,_:Int)
    val a: Int => Int = $Lambda$1114/1028466661@5dd12d01
    
    scala> a(9)
    val res23: Int = 12
    

    定义一个新的变量a, 它将原本sum函数的第1、2个传入参数的值固定了,然后我们调用的时候只需要向 a 传入原本sum函数中的第3个参数就可以了。

  2. Curry化

    Curry化的函数是指一个函数拥有多个参数列表,并且每个参数列表中只有一个参数。

针对容器的操作

  1. 遍历

    scala标准的容器遍历方法为foreach方法。该方法的原型为:def foreach[U](f: Elem => U): Unit

    先来看一个例子:

    scala> var list = List(1,2,3)         //创建一个List容器
    var list: List[Int] = List(1, 2, 3)
    
    scala> var f = (i:Int) => println(i)
    var f: Int => Unit = $Lambda$1122/1187220855@4ad30ac5
    
    scala> list.foreach(f)
    1
    2
    3
    

    可以看到,foreach方法返回值类型为Unit,该函数接受一个函数f 作为参数。

    函数f 的传入参数类型为Elem,即容器中元素的类型;然后返回值类型为Unit。

    在这个例子中,我们遍历了List容器中的元素,其元素类型为Int。传入foreach函数的函数参数 f 的功能是打印元素。

    我们还可以使用中缀表示法来调用 foreach:格式为容器 foreach 应用在容器上的方法

    scala> list foreach println
    1
    2
    3
    
  2. 映射

    映射是指通过对容器中的元素进行某些运算来生成一个新的容器。scala中有两个典型的映射方法:map 方法和flatMap方法。

    • map方法对集合中每个元素进行指定运算生成新的元素,会返回一个与原容器同样类型和大小的新容器。

      scala> val bao = List("chen","rui","bo")
      val bao: List[String] = List(chen, rui, bo)
      
      scala> bao.map(s => s.length)
      val res32: List[Int] = List(4, 3, 2)
      
    • flatMap方法对集合中每个元素进行指定运算,然后对于每个元素都会返回一个容器,最后把生成的所有容器合并成为一个容器并返回。返回容器的类型与原容器相同,但大小可能不同,其中元素类型也可能不同。

      scala> bao.flatMap(s => s.toList)
      val res36: List[Char] = List(c, h, e, n, r, u, i, b, o)
      

      ps: List是一个列表,会存储重复元素。

  3. 过滤

    过滤,顾名思义就是根据实际需求对容器中的元素进行筛选。scala中最经典的过滤方法是 filter,exists以及find 。

    下面举个例子来看看怎么使用filter :

    scala> val university = Map("XMU"->"Xiamen University","THU"->"Tsinghua University", "PKU"->"Peking University")
    val university: scala.collection.immutable.Map[String,String] = Map(XMU -> Xiamen University, THU -> Tsinghua University, PKU -> Peking University)
    
    scala> f = university filter{kv => kv._2 contains "Xiamen"} 
    val res39: scala.collection.immutable.Map[String,String] = Map(XMU -> Xiamen University)
    

    整个过程就是,首先创建一个Map容器,然后遍历整个容器,查找值含有"Xiamen"的键值对,进行过滤,得到一个新的Map容器。其中kv._2用于访问某个键值对的值,相应地,如果要访问该键值对的键,则使用kv._1

    类似的,exists 用于查找容器中是否含有某个元素,find 用于查找第一个符合条件的元素。

  4. 规约

    规约是对容器的元素进行两两运算,将其“规约”为一个值。

    先来看看最常用的reduce 方法:

    scala> val list = List(1,2,3,4,5)
    val list: List[Int] = List(1, 2, 3, 4, 5)
    
    scala> list.reduce(_+_)
    val res41: Int = 15
    

    reduce接收了一个二元函数f (带有2个元素) 作为参数,然后进行运算,最后结果为15,这个过程可以表示为:((((1+2)+3)+4)+5)

    所以reduce第一次接受的元素分别是1和2,然后是3和3,接着是6和4,最后是10和5,每次都将接受的两个元素相加,然后再将相加的结果作为下一次接收的两个元素中的第一个,规约操作进行到容器中的元素遍历完成为止。

    • 对于List这样的元素有序的容器做规约,reduce默认的顺序为从左到右,若要自定义遍历顺序,可以使用reduceLeft或者reduceRight。规约结果跟进行的运算以及规约顺序也有关系,加法和乘法遵循交换律和结合律,从左到右和从右到左并没有区别,而对于减法就有区别了。
    • 对于Set这种元素无序的容器做规约,由于顺序未定,其最后结果可能是不确定的。
    • 与reduce相似的规约方法还有fold。

  5. 拆分

    拆分操作将容器中的元素按照一定规则分割成多个子容器。常用的拆分方法有:partition、groupedBy、grouped和sliding。

    • partition方法接受一个布尔函数,遍历容器并使用该布尔函数判断每个元素是否符合条件,然后以二元组的方式返回两个容器,分别为满足条件的元素集合和不满足条件的元素集合。

      scala> val list = List(1,2,3,4,5,6,7)
      val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
      
      scala> list.partition(_<4)
      val res42: (List[Int], List[Int]) = (List(1, 2, 3),List(4, 5, 6, 7))
      
    • groupedBy方法接收一个返回Unit类型的函数,用该函数对容器元素进行遍历,将返回值相同的元素作为一个子容器,并与该相同的值构成一个HashMap键值对,最后返回的形式为:Map[U,C[T]]。

      scala> val gby = list.groupBy(x=>x%3)
      val gby: scala.collection.immutable.Map[Int,List[Int]] = HashMap(0 -> List(3, 6), 1 -> List(1, 4, 7), 2 -> List(2, 5))
      
    • grouped 方法接收一个整型参数 n,按从左到右的顺序将容器划分为多个大小为n的子容器,最后一个容器的大小可能不足n,然后返回由子容器构成的迭代器。

      scala> val group = list.grouped(2)
      val group: Iterator[List[Int]] = <iterator>
      
      scala> group foreach println
      List(1, 2)
      List(3, 4)
      List(5, 6)
      List(7)
      
    • sliding方法接收一个整型参数 n,按从左到右的顺序将容器截取为多个长度为n的滑动窗口,返回由子容器构成的迭代器。

      scala> val slidingwindow = list.sliding(3)
      val slidingwindow: Iterator[List[Int]] = <iterator>
      
      scala> slidingwindow foreach println
      List(1, 2, 3)
      List(2, 3, 4)
      List(3, 4, 5)
      List(4, 5, 6)
      List(5, 6, 7)
      

参考:《Spark编程基础(Scala版)》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值