温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Scala函数式编程基础讲解

发布时间:2021-07-20 11:44:06 来源:亿速云 阅读:187 作者:chen 栏目:大数据

这篇文章主要讲解了“Scala函数式编程基础讲解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Scala函数式编程基础讲解”吧!

3 函数式编程基础


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

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

函数的定义与使用

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

  • 类型

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

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

比如:

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

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

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

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

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

scala> def mult(factor:Int)(x:Int) = x*factordef mult(factor: Int)(x: Int): Intscala> 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): Intscala> 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): Intscala> sum(1,2,3)val res21: Int = 6scala> sum(1,2,4)val res22: Int = 7scala> val a = sum(1,2,_:Int)val a: Int => Int = $Lambda$1114/1028466661@5dd12d01scala> 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@4ad30ac5scala> list.foreach(f)123


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

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

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

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

    scala> list foreach println123


  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)

        

感谢各位的阅读,以上就是“Scala函数式编程基础讲解”的内容了,经过本文的学习后,相信大家对Scala函数式编程基础讲解这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI