函数式编程 best ref
1.函数定义和高阶函数
函数的类型和值
- 在非函数式编程语言里,函数的定义包含了**“函数类型”和“值”**两种层面的内容。
- 在函数式编程中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作,也就是说,函数的使用方式和其他数据类型的使用方式完全一致。
def counter(value: Int): Int = { value += 1} // 这个函数的“类型” (Int) => Int 当参数只有一个时,括号可以省略
// 只要把函数定义中的类型声明部分去除,剩下的就是函数的“值”
(value) => {value += 1} //只有一条语句时,大括号可以省略
// 上面就是函数的“值”,需要注意的是,采用“=>”而不是“=”,这是Scala的语法要求。
// 我们也可以按照上面类似的形式来定义Scala中的函数:
val counter: Int => Int = { (value) => value += 1 } // 这是函数定义最完整形式,牢记,有各种省略表示法
// counter 是一个函数变量,对比字符串型变量
Lambda表达式
//我们不需要给每个函数命名,这时就可以使用匿名函数,如下:
(num: Int) => num * 2 // 我们经常称为 Lambda表达式
val myNumFunc: Int=>Int = (num: Int) => num * 2
println(myNumFunc(3)) // 传入3,得到乘法结果是6
// Scala具有类型推断机制,可以自动推断变量类型
val myNumFunc = (num: Int) => num * 2 // 常用形式
val myNumFunc= (num) => num * 2 // error: missing parameter type 全部省略以后,解释器也无法推断出类型
val myNumFunc: Int=>Int = (num) => num * 2
- 闭包是一个函数,一种比较特殊的函数, 闭包会引用函数外部的变量
val addMore=(x:Int)=>x+more // 闭包定义的实例
// more并没有在函数中定义,是一个函数外部的变量
高阶函数
- 函数在Scala中是“头等公民”,它的使用方法和任何其他变量是一样的。一个接受其他函数作为参数或者返回一个函数的函数就是高阶函数。
//定义了一个新的函数sum,以函数f为参数 函数sum是一个接受函数参数的函数,因此,是一个高阶函数。
def sum(f: Int => Int, a: Int, b: Int): Int ={
if(a > b) 0 else f(a) + sum(f, a+1, b)
} // 函数sum的类型是 (Int=>Int, Int, Int) => Int
//定义了一个新的函数self,该函数的输入是一个整数x,然后直接输出x自身
def self(x: Int): Int = x
//定义sumInts函数,调用了函数f 和 函数sum
def sumInts(a: Int, b: Int): Int = sum(self, a, b)
函数和方法的区别
- Scala中的方法跟Java的方法一样。Scala中的函数是一个完整的对象, Scala中用22个特质(trait)抽象出了函数的概念。ref
- 方法名是方法调用,而函数名只是代表函数对象本身
2.占位符 “_” 语法
-
下划线符号“_”在Scala语言中经常会用到,并且出现的场景千变万化。ref
-
为了让函数字面量更加简洁,我们可以使用下划线作为一个或多个参数的占位符,只要每个参数在函数字面量内仅出现一次。
- 匿名函数参数占位符
当匿名函数传递给方法或其他函数时,如果该匿名函数的参数在=>的右侧只出现一次,那么就可以省略=>,并将参数用下划线代替。这对一元函数和二元函数都适用。
val list = List(5,3,7,9,1)
list.map(_ * 10) // list.map(x => x * 10)
list.sortWith(_ < _) // list.sortWith((x, y) => x < y)
list.reduceLeft(_ + _) // list.reduceLeft((x, y) => x + y)\
// 当匿名函数的参数未被实际使用到时,可以不给它一个命名,而直接用下划线代替
list.foreach(_ => println("Hello Scala")) // 输出 5 个 Hello Scala
- 通配符
// 泛型定义中的通配符 在Java中用问号来指代泛型中不确定类型的定义(如List<?>)。Scala用下划线来代替它
def testPrint(l: List[_]) = {
list.foreach(x => println(x)) }
// 模式匹配中的通配符/占位符
expr match {
// 以1开头,且长度为3的List
case List(1,_,_) => "a list with three element and the first element is 1"
// 长度大于等于0的List
case List(_*) => "a list with zero or more elements"
// 键和值类型都为任意类型的Map
case Map[_,_] => "matches a map with any key type and any value type"
case _ =>
}
// 导入语句中的通配符 下划线可以实现Java import语句中星号的作用,但功能更强大一些。利用它还能导入时做重命名,以及忽略某些类。
import java.lang.Math._ // import static java.lang.Math.*
import java.util.{ArrayList => al, _} // import java.util.*,并将ArrayList重命名为al
import java.util.{Timer => _, _} // import java.util.*,但不导入Timer类
- 变量默认值初始化
- 用下划线可以自动在变量声明时,将其赋予默认的初始值
var name : String = _ // name: String = null
var count : Int = _ // count: Int = 0
var avg : Double = _ // avg: Double = 0.0
- 访问元组(tuple)
- 下划线后面跟上数字k,可以当作索引表示元组中的第k个元素。当要忽略元组中的某个值时,也可以用下划线代替它
val tuple = ("LMagics", 173.5, Seq(22,66,88))
tuple._1 // LMagics
- 变长参数的转化
- 下划线与星号连用,可以将序列转化为变长参数的序列,方便调用变长参数的方法。
def sum(args: Int*) = {
| var result = 0
| for (arg <- args) result += arg
| result }
val sum1 = sum(7,8,9,10,11,12,13,14) // 84
val sum2 = sum(7 to 14: _*) // 84 // 如果只写7 to 14,会报错
- 将方法转换成函数
- Scala中方法与函数不同,函数在Scala中是一种对象实例,因此它可以赋值给变量,也可以作为参数。如果方法在赋值时直接写名称的话,编译器会认为是对方法的调用,因此会报没有参数列表的错误。
- 在方法名称后加一个下划线,会将其转化为偏应用函数(partially applied function),就能直接赋值了
def twoSum(a: Int, b: Int) = a + b // twoSum: (a: Int, b: Int)Int
val twoSumFunc = twoSum _ // twoSumFunc: (Int, Int) => Int = <function2>
3.针对集合的操作
1.遍历操作
val list = List(1, 2, 3, 4, 5)
for (elem <- list) println(elem)
list.foreach(elem => println(elem)) //本行语句甚至可以简写为list.foreach(println),或者写成:list foreach println
val university = Map( "THU" -> "Tsinghua University","PKU"->"Peking University")
for ((k,v) <- university) printf("Code is : %s and name is: %s\n",k,v)
university foreach {kv => println(kv._1+":"+kv._2)}
2.map操作
-
map操作是针对集合的典型变换操作,它将某个函数应用到集合中的每个元素,并产生一个结果集合
-
flatMap是map的一种扩展。在flatMap中,我们会传入一个函数,该函数对每个输入都会返回一个集合(而不是一个元素),然后,flatMap把生成的多个集合“拍扁”成为一个集合。
val books = List("Hadoop", "Hive", "HDFS")
books.map(s => s.toUpperCase) // List(HADOOP, HIVE, HDFS)
books flatMap (s => s.toList) // List(H, a, o, o, p, H, i, v, e, H, D, F, S)
books.flatMap(s => s.toList) // 等价于 books flatMap (s => s.toList)
3.filter操作
- 遍历一个集合并从中获取满足指定条件的元素组成一个新的集合。Scala中可以通过filter操作来实现
val universityOfXiamen = university.filter{kv => kv._2 contains "Xiamen"}
val universityOfP = university.filter{kv => kv._2 startsWith "P"} // 学校名称中以字母“P”开头的元素
4.reduce操作
- scala当中的reduce可以对集合当中的元素进行归约操作。reduce包含reduceLeft和reduceRight。reduceLeft就是从左向右归约,reduceRight就是从右向左归约。 参考
val a = Array(12, 6, 15, 2, 20, 9)
a.reduceLeft(_ + _) // 64 是这样的:12+6=18, 18+15=33, 33+2=35, 35+20=55, 55+9=64,就是对集合的所有元素求和
a.reduceLeft(_ * _) // 所有乘积
a.reduceLeft(_ min _) // 2 最小值
a.reduceLeft(_ max _) // 20 最大值
val list = List(1,2,3,4,5) // reduce默认为reduceLeft
list.reduce(_ - _) // -13
5.fold操作
- 折叠(fold)操作和reduce(归约)操作比较类似。fold操作需要从一个初始的“种子”值开始,并以该值作为上下文,处理集合中的每个元素。
val list = List(1,2,3,4,5)
list.fold(10)(_*_) // fold函数实现了对list中所有元素的累乘操作。
// fold函数需要两个参数,一个参数是初始种子值,这里是10,另一个参数是用于计算结果的累计函数,这里是累乘。 首先把初始值拿去和list中的第一个值1做乘法操作
// fold有两个变体:foldLeft()和foldRight()
WordCount实例
import java.io.File
import scala.io.Source
object WordCount {
def main(args: Array[String]): Unit = {
val dirfile = new File("/usr/local/scala/mycode/wordcount")
val files = dirfile.listFiles
for (file <-files) println(file)
val listFiles = files.toList
val wordsMap = scala.collection.mutable.Map[String,Int]()
listFiles.foreach(
file => Source.fromFile(file).getLines().foreach(
line=>line.split(" ").foreach(word=>{
if(wordsMap.contains(word)) {wordsMap(word)+=1} else {wordsMap+=(word->1) } } ) ) )
} // main
println(wordsMap)
for((key,value)<-wordsMap ) println(key+": "+value)
} //object