Scala入门到精通——第十三节 高阶函数

本节主要内容

  1. 高阶函数简介
  2. Scala中的常用高阶函数
  3. SAM转换
  4. 函数柯里化
  5. 偏函数

1. 高阶函数简介

高阶函数主要有两种:一种是将一个函数当做另外一个函数的参数(即函数参数);另外一种是返回值是函数的函数。这两种在本教程的第五节 函数与闭包中已经有所涉及,这里简单地回顾一下:

(1)函数参数

//函数参数,即传入另一个函数的参数是函数
//((Int)=>String)=>String
scala> def convertIntToString(f:(Int)=>String)=f(4)
convertIntToString: (f: Int => String)String

scala> convertIntToString((x:Int)=>x+" s")
res32: String = 4 s

(2)返回值是函数的函数

//高阶函数可以产生新的函数,即我们讲的函数返回值是一个函数
//(Double)=>((Double)=>Double)
scala>  def multiplyBy(factor:Double)=(x:Double)=>factor*x
multiplyBy: (factor: Double)Double => Double

scala> val x=multiplyBy(10)
x: Double => Double = <function1>

scala> x(50)
res33: Double = 500.0   

Scala中的高阶函数可以说是无处不在,这点可以在Scala中的API文档中得到验证,下图给出的是Array数组的需要函数作为参数的API:

例如flatMap方法,下面是其API的详细内容:

def
flatMap[B](f: (A) ? GenTraversableOnce[B]): Array[B]
[use case]
Builds a new collection by applying a function to all elements of this array and using the elements of the resulting collections.

//下面的代码给出了该函数的用法
For example:

def getWords(lines: Seq[String]): Seq[String] = lines flatMap (line => line split "\\W+")
The type of the resulting collection is guided by the static type of array. This might cause unexpected results sometimes. For example:

// lettersOf will return a Seq[Char] of likely repeated letters, instead of a Set
def lettersOf(words: Seq[String]) = words flatMap (word => word.toSet)

// lettersOf will return a Set[Char], not a Seq
def lettersOf(words: Seq[String]) = words.toSet flatMap (word => word.toSeq)

// xs will be a an Iterable[Int]
val xs = Map("a" -> List(11,111), "b" -> List(22,222)).flatMap(_._2)

// ys will be a Map[Int, Int]
val ys = Map("a" -> List(1 -> 11,1 -> 111), "b" -> List(2 -> 22,2 -> 222)).flatMap(_._2)
//下面几行对该函数的参数进行了说明
B
the element type of the returned collection.
//指明f是函数,该函数传入的参数类型是A,返回类型是GenTraversableOnce[B]
f
the function to apply to each element.
returns
a new array resulting from applying the given collection-valued function f to each element of this array and concatenating the results.

2. Scala中的常用高阶函数

1 map函数

所有集合类型都存在map函数,例如Array的map函数的API具有如下形式:

def map[B](f: (A) ? B): Array[B]
用途:Builds a new collection by applying a function to all elements of this array.
B的含义:the element type of the returned collection.
f的含义:the function to apply to each element.
返回:a new array resulting from applying the given function f to each element of this array and collecting the results.
//这里面采用的是匿名函数的形式,字符串*n得到的是重复的n个字符串,这是scala中String操作的一个特点
scala> Array("spark","hive","hadoop").map((x:String)=>x*2)
res3: Array[String] = Array(sparkspark, hivehive, hadoophadoop)

//在函数与闭包那一小节,我们提到,上面的代码还可以简化
//省略匿名函数参数类型
scala> Array("spark","hive","hadoop").map((x)=>x*2)
res4: Array[String] = Array(sparkspark, hivehive, hadoophadoop)

//单个参数,还可以省去括号
scala> Array("spark","hive","hadoop").map(x=>x*2)
res5: Array[String] = Array(sparkspark, hivehive, hadoophadoop)

//参数在右边只出现一次的话,还可以用占位符的表示方式
scala> Array("spark","hive","hadoop").map(_*2)
res6: Array[String] = Array(sparkspark, hivehive, hadoophadoop)

List类型:

scala> val list=List("Spark"->1,"hive"->2,"hadoop"->2)
list: List[(String, Int)] = List((Spark,1), (hive,2), (hadoop,2))

//写法1
scala> list.map(x=>x._1)
res20: List[String] = List(Spark, hive, hadoop)
//写法2
scala> list.map(_._1)
res21: List[String] = List(Spark, hive, hadoop)

scala> list.map(_._2)
res22: List[Int] = List(1, 2, 2)

Map类型:

//写法1
scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(_._1)
res23: scala.collection.immutable.Iterable[String] = List(spark, hive, hadoop)

scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(_._2)
res24: scala.collection.immutable.Iterable[Int] = List(1, 2, 3)

//写法2
scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(x=>x._2)
res25: scala.collection.immutable.Iterable[Int] = List(1, 2, 3)

scala> Map("spark"->1,"hive"->2,"hadoop"->3).map(x=>x._1)
res26: scala.collection.immutable.Iterable[String] = List(spark, hive, hadoop)

2 flatMap函数

//写法1
scala> List(List(1,2,3),List(2,3,4)).flatMap(x=>x)
res40: List[Int] = List(1, 2, 3, 2, 3, 4)

//写法2
scala> List(List(1,2,3),List(2,3,4)).flatMap(x=>x.map(y=>y))
res41: List[Int] = List(1, 2, 3, 2, 3, 4)

3 filter函数

scala> Array(1,2,4,3,5).filter(_>3)
res48: Array[Int] = Array(4, 5)

scala> List("List","Set","Array").filter(_.length>3)
res49: List[String] = List(List, Array)

scala> Map("List"->3,"Set"->5,"Array"->7).filter(_._2>3)
res50: scala.collection.immutable.Map[String,Int] = Map(Set -> 5, Array -> 7)

4 reduce函数

//写法1
scala> Array(1,2,4,3,5).reduce(_+_)
res51: Int = 15

scala> List("Spark","Hive","Hadoop").reduce(_+_)
res52: String = SparkHiveHadoop

//写法2
scala> Array(1,2,4,3,5).reduce((x:Int,y:Int)=>{println(x,y);x+y})
(1,2)
(3,4)
(7,3)
(10,5)
res60: Int = 15

scala> Array(1,2,4,3,5).reduceLeft((x:Int,y:Int)=>{println(x,y);x+y})
(1,2)
(3,4)
(7,3)
(10,5)
res61: Int = 15

scala> Array(1,2,4,3,5).reduceRight((x:Int,y:Int)=>{println(x,y);x+y})
(3,5)
(4,8)
(2,12)
(1,14)
res62: Int = 15

5 fold函数

scala> Array(1,2,4,3,5).foldLeft(0)((x:Int,y:Int)=>{println(x,y);x+y})
(0,1)
(1,2)
(3,4)
(7,3)
(10,5)
res66: Int = 15

scala> Array(1,2,4,3,5).foldRight(0)((x:Int,y:Int)=>{println(x,y);x+y})
(5,0)
(3,5)
(4,8)
(2,12)
(1,14)
res67: Int = 15

scala> Array(1,2,4,3,5).foldLeft(0)(_+_)
res68: Int = 15

scala> Array(1,2,4,3,5).foldRight(10)(_+_)
res69: Int = 25

// /:相当于foldLeft
scala> (0 /: Array(1,2,4,3,5)) (_+_)
res70: Int = 15

scala> (0 /: Array(1,2,4,3,5)) ((x:Int,y:Int)=>{println(x,y);x+y})
(0,1)
(1,2)
(3,4)
(7,3)
(10,5)
res72: Int = 15

6 scan函数

//从左扫描,每步的结果都保存起来,执行完成后生成数组
scala> Array(1,2,4,3,5).scanLeft(0)((x:Int,y:Int)=>{println(x,y);x+y})
(0,1)
(1,2)
(3,4)
(7,3)
(10,5)
res73: Array[Int] = Array(0, 1, 3, 7, 10, 15)

//从右扫描,每步的结果都保存起来,执行完成后生成数组
scala> Array(1,2,4,3,5).scanRight(0)((x:Int,y:Int)=>{println(x,y);x+y})
(5,0)
(3,5)
(4,8)
(2,12)
(1,14)
res74: Array[Int] = Array(15, 14, 12, 8, 5, 0)

3. SAM转换

在java的GUI编程中,在设置某个按钮的监听器的时候,我们常常会使用下面的代码(利用scala进行代码开发):

var counter=0;
val button=new JButton("click")
button.addActionListener(new ActionListener{
   override def actionPerformed(event:ActionEvent){
   counter+=1
   }
})

上面代码在addActionListener方法中定义了一个实现了ActionListener接口的匿名内部类,代码中

new ActionListener{
   override def actionPerformed(event:ActionEvent){

 }

这部分称为样板代码,即在任何实现该接口的类中都需要这样用,重复性较高,由于ActionListener接口只有一个actionPerformed方法,它被称为simple abstract method(SAM)。SAM转换是指只给addActionListener方法传递一个参数

button.addActionListener((event:ActionEvent)=>counter+=1)

//并提供一个隐式转换,我们后面会具体讲隐式转换
implict def makeAction(action:(event:ActionEvent)=>Unit){
   new ActionListener{
     override def actionPerformed(event:ActionEvent){action(event)}
}

这样的话,在进行GUI编程的时候,可以省略非常多的样板代码,使代码更简洁。

4. 函数柯里化

在函数与闭包那一节中,我们定义了下面这样的一个函数

//mutiplyBy这个函数的返回值是一个函数
//该函数的输入是Doulbe,返回值也是Double
scala>  def multiplyBy(factor:Double)=(x:Double)=>factor*x
multiplyBy: (factor: Double)Double => Double

//返回的函数作为值函数赋值给变量x
scala> val x=multiplyBy(10)
x: Double => Double = <function1>

//变量x现在可以直接当函数使用
scala> x(50)
res33: Double = 500.0   

上述代码可以像这样使用:

scala> def multiplyBy(factor:Double)=(x:Double)=>factor*x
multiplyBy: (factor: Double)Double => Double

//这是高阶函数调用的另外一种形式
scala> multiplyBy(10)(50)
res77: Double = 500.0

那函数柯里化(curry)是怎么样的呢?其实就是将multiplyBy函数定义成如下形式

scala> def multiplyBy(factor:Double)(x:Double)=x*factor
multiplyBy: (factor: Double)(x: Double)Double

即通过(factor:Double)(x:Double)定义函数参数,该函数的调用方式如下:

//柯里化的函数调用方式
scala> multiplyBy(10)(50)
res81: Double = 500.0

//但此时它不能像def multiplyBy(factor:Double)=(x:Double)=>factor*x函数一样,可以输入单个参数进行调用
scala> multiplyBy(10)

<console>:10: error: missing arguments for method multiplyBy;
follow this method with `_‘ if you want to treat it as a partially applied funct
ion
              multiplyBy(10)
                        ^

错误提示函数multiplyBy缺少参数,如果要这么做的话,需要将其定义为偏函数

scala> multiplyBy(10)_
res79: Double => Double = <function1>

那现在我们接着对偏函数进行介绍

5. 偏函数

在数组那一节中,我们讲到,Scala中的数组可以通过foreach方法将其内容打印出来,代码如下:

scala>Array("Hadoop","Hive","Spark")foreach(x=>println(x))
Hadoop
Hive
Spark
//上面的代码等价于下面的代码
scala> def print(x:String)=println(x)
print: (x: String)Unit

scala> Array("Hadoop","Hive","Spark")foreach(print)
Hadoop
Hive
Spark

那什么是偏函数呢,所谓偏函数就是指,当函数有多个参数,而在我们使用该函数时我们不想提供所有参数(假设函数有3个函数),只提供0~2个参数,此时得到的函数便是偏函数,定义上述print函数的偏函数代码如下:

//定义print的偏函数
scala> val p=print _
p: String => Unit = <function1>

scala> Array("Hadoop","Hive","Spark")foreach(p)
Hadoop
Hive
Spark

scala> Array("Hadoop","Hive","Spark")foreach(print _)
Hadoop
Hive
Spark

在上面的简化输出代码中,下划线_并不是占位符的作用,而是作为偏函数的定义符。前面我演示了一个参数的函数偏函数的定义方式,现在我们定义一个多个输入参数的函数,代码如下:

//定义一个求各函数
scala> def sum(x:Int,y:Int,z:Int)=x+y+z
sum: (x: Int, y: Int, z: Int)Int

//不指定任何参数的偏函数
scala> val s1=sum _
s1: (Int, Int, Int) => Int = <function3>

scala> s1(1,2,3)
res91: Int = 6

 //指定两个参数的偏函数
scala> val s2=sum(1,_:Int,3)
s2: Int => Int = <function1>

scala> s2(2)
res92: Int = 6

//指定一个参数的偏函数
scala> val s3=sum(1,_:Int,_:Int)
s3: (Int, Int) => Int = <function2>

scala> s3(2,3)
res93: Int = 6

在函数柯里化那部分,我们提到柯里化的multiplyBy函数输入单个参数,它并不会像没有柯里化的函数那样返回一个函数,而是会报错,如果需要其返回函数的话,需要定义其偏函数,代码如下:

//定义multiplyBy函数的偏函数,它返回的是一个函数
scala> val m=multiplyBy(10)_
m: Double => Double = <function1>

scala> m(50)
res94: Double = 500.0

添加公众微信号,可以了解更多最新Spark、Scala相关技术资讯

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-11 06:20:37

Scala入门到精通——第十三节 高阶函数的相关文章

Scala入门到精通——第二十八节 Scala与JAVA互操作

本节主要内容 JAVA中调用Scala类 Scala中调用JAVA类 Scala类型参数与JAVA泛型互操作 Scala与Java间的异常处理互操作 1. JAVA中调用Scala类 Java可以直接操作纵Scala类,如同Scala直接使用Java中的类一样,例如: //在Person.scala文件中定义Scala语法的Person类 package cn.scala.xtwy.scalaToJava class Person(val name:String,val age:Int) //伴

十:高阶函数和递归函数

一:高阶函数: def:高阶函数简单来说就是在计算中用到的二次方,三次方等之类的函数,我们可以直接通过函数调用来实现. 1 # 高阶函数 2 3 def f(n): 4 return n * n 5 6 7 def foo(a,b,func): # 可以把函数作为参数传入另一个函数中 8 ret = func(a) + func(b) 9 return ret 10 11 print(foo(1,2,f)) 二:递归函数 def:递归函数就是一个函数在自己内部调用了自己,对于这类函数要找到递归函

python六十课——高阶函数之map

1.高阶函数: 特点:函数的形参位置必须接受一个函数对象 分类学习: 1).map(fn,lsd1,[lsd2...]): 参数一:fn --> 函数对象 参数二:lsd1 --> 序列对象(字符串.列表.range...) 功能: 将fn函数作用于lsd1中的每一个元素上, 将每次执行的结果存入到一个map对象中返回: [注意]得到的这个map对象是一个迭代器对象 需求:lt = ['1','2','3','4','5'] --> [1,2,3,4,5] map(int,lt):执行过

Scala入门到精通——第二十五节 提取器(Extractor)

作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 apply与unapply方法 零变量或变量的模式匹配 提取器与序列模式 scala中的占位符使用总结 1. apply与unapply方法 apply方法我们已经非常熟悉了,它帮助我们无需new操作就可以创建对象,而unapply方法则用于析构出对象,在模式匹配中特别提到,如果一个类要能够应用于模式匹配当中,必须将类声明为case class,因为一旦被定义为case class,

Scala入门到精通——第二十四节 高级类型 (三)

作者:摆摆少年梦 视频地址:http://blog.csdn.net/wsscy2004/article/details/38440247 本节主要内容 Type Specialization Manifest.TypeTag.ClassTag Scala类型系统总结 在scala中,类(class)与类型(type)是两个不一样的概念.我们知道类是对同一类型数据的抽象,而类型则更详细. 比方定义class List[T] {}, 能够有List[Int] 和 List[String]等详细类型

Scala入门到精通——第十四节 Case Class与模式匹配(一)

本节主要内容 模式匹配入门 Case Class简介 Case Class进阶 1. 模式匹配入门 在java语言中存在switch语句,例如: //下面的代码演示了java中switch语句的使用 public class SwitchDemo { public static void main(String[] args) { for(int i = 0; i < 100; i++) { switch (i) { case 10:System.out.println("10"

Scala入门到精通——第十五节 Case Class与模式匹配(二)

本节主要内容 模式匹配的类型 for控制结构中的模式匹配 option类型模式匹配 1. 模式的类型 1 常量模式 object ConstantPattern{ def main(args: Array[String]): Unit = { //注意,下面定义的是一个函数 //函数的返回值利用的是模式匹配后的结果作为其返回值 //还需要注意的是函数定义在main方法中 //也即scala语言可以在一个函数中定义另外一个函数 def patternShow(x:Any)=x match { ca

Scala入门到精通——第十节 Scala类层次结构、Traits初步

本节主要内容 Scala类层次结构总览 Scala中原生类型的实现方式解析 Nothing.Null类型解析 Traits简介 Traits几种不同使用方式 1 Scala类层次结构 Scala中的类层次结构图如下: 来源:Programming in Scala 从上面的类层次结构图中可以看到,处于继承层次最顶层的是Any类,它是scala继承的根类,scala中所有的类都是它的子类 Any类中定义了下面几个方法: //==与!=被声明为final,它们不能被子类重写 final def ==

Scala入门到精通——第二十节 类型參数(二)

本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对scala中的Ordering与Ordered之间的关联与差别进行解说,先看Ordering.Ordered的类继承层次体系: 通过上面两个图能够看到,Ordering混入了java中的Comparator接口.而Ordered混入了java的Comparable接口.我们知道java中的Comparator是一