第三讲 Scala集合入门和函数式编程(上)
一、Scala集合入门
scala的集合分为了两类,一类是可变的集合(集合可以执行增删改查操作),另一类是不可变集合(集合元素在初始化的时候确定,后续只能进行查,有的可以进行修改,有的不可以)。二者可能名称一样,但是在不同的包下面,对应的包为:scala.collection.mutable和scala.collection.immutable。
scala默认使用的集合,或者默认导入的包是immutable。
说明:这里提到的可变或者不可变,指的是容器内部的内容,以及容器的长度可变或者不可变。
(一)、Scala数组 1、不可变数组Array
Array可以理解为java中的数组。
(1)数组的定义
java中数组的定义:
new int[5];
int[] aa = {1, 2, 3};
int[] bb = new int[]{1, 2, 3, 4}
scala中数组的定义:
val array = new Array[类型(比如Int/Long)](5) --->定义了一个长度为5的Int/Long类型的数组,每一个元素都有默认值:0。
val array = Array(1, 2, 3, 4, 5) -->定义类一个初始化内容为1, 2, 3, 4, 5的Int类型的数组。
其中第二种的定义方式,没有new关键,其实是class Array的伴生对象的构建方式。
(2)获取数组中的元素
|
数组名(index) 比如: val ele = array(3) |
(3)元素的遍历
|
for(ele <- 数组) { 。。。 } for(i <- array) { println(i) } |
(4)数组的长度
|
array.length array.size |
(5)数组判断
|
判断元素是否包含 array.contains(ele) eg: println("判断元素-3时候在数组中存在:" + array.contains(-2)) |
(6)数组元素的拼接输出
|
使用数组的方法:mkString //拼接数组元素,没有分隔符 println("数组元素:" + array.mkString) //拼接数组元素,使用分隔符", " println("数组元素:" + array.mkString(", ")) //拼接数组元素,使用分隔符", ",起始字符为[,终止字符为] println("数组元素:" + array.mkString("[", ", ", "]")) ========================================= 数组元素:123-35 数组元素:1, 2, 3, -3, 5 数组元素:[1, 2, 3, -3, 5] |
ArrayBuffer就可以理解为java中的ArrayList。
(1)ArrayBuffer的定义
|
//定义一个Int类型的可变数组 val arrayBuffer = new ArrayBuffer[Int]() //定义一个String类型的可变数组 val arrayBuffer = ArrayBuffer[String]() //定义一个String类型的可变数组,小括号内是元素 val arrayBuffer = ArrayBuffer[String](“hello”) |
(2)crud
|
//增 println("----------arraybuffer的增的操作--------------") ab.append(1) println("ab.append(1): " + ab) ab.append(2, 3, 4) println("ab.append(2, 3, 4): " + ab) //insert(index, ele*)在指定的索引位置上插入一个或多个元素 ab.insert(2, -3, -2) println("ab.insert(2, -3, -2): " + ab) //查 //获取指定索引位置的元素 val ele = ab(5) println("ab(5): " + ele) //数组长度 println("可变数组ab的长度:" + ab.size) //改 ab(5) = -5 println("修改之后的数组为:" + ab) //删 ab.remove(3)//删除指定索引位置上的元素 println("ab.remove(3): " + ab) ab.remove(2, 2)//remove(index, count)从指定索引index位置开始删除,删除count个元素 println("ab.remove(2, 2): " + ab) |
|
//drop var newAb = ab.drop(2) println("ab.drop(2): " + ab) println("ab.drop(2): " + newAb) newAb = ab.dropRight(2) println("ab.dropRight(2): " + ab) println("ab.dropRight(2): " + newAb) |
(2)元素的遍历
遍历和Array方式一样。
长度可以使用length,也可以使用size。
|
for (i <- ab) { print(i + "t") } |
(3)拼接字符串
|
ab.mkString("[", ", ", "]") |
3、数组通用操作
(1)包含
|
ab.contains(ele) 判断元素是否包含 |
(2)、数组求和
|
调用数组的函数sum println("数组求和" + ab.sum) |
(3)、数组的最大值最小值
|
调用数组的函数max和min println("最大值:" + ab.max) println("最小值:" + ab.min) |
(4)、Array和ArrayBuffer之间的互相转换
|
java版本 数组--->List:Arrays.asList(array) List--->数组:list.toArray(new Xxx[list.size()]) scala版本 Array--->ArrayBuffer:array.toBuffer ArrayBuffer--->Array:ab.toArray |
(5)Array和ArrayBuffer之间的拼接
|
mkString(start, sep, end) |
(二)、Scala Tuple 1、定义
元组,其实就是一组对偶,也可以理解为是java中List
2、创建并初始化
说明:元组Tuple是List
//元组只能在创建的时候进行初始化 val tuple1 = new Tuple1[Int](1) val tuple2 = new Tuple2[Int, Double](1, 3.14)3、操作
|
//获取元组的值 //获取第一个 println("tuple2._1: " + tuple2._1) //获取第二个 println("tuple2._2: " + tuple2._2) //获取第N(1 <= N <= 22)个 // tuple2._N // tuple2._2 = 4.35 //不可以修改 //遍历 元组不可以直接进行遍历 for(t <- tuple2.productIterator) { println(t) } |
4、比较常见的定义元组的方式
|
//更常见的元组的定义 val season = ("spring", "summer", "autumn", "winter") season._2 //最常见的定义方式 val (spring, summer, autumn, winter) = ("spring", "summer", "autumn", "winter") println(spring) println(summer) |
(三)、Zip拉链操作
zip操作,就是将两个单列的集合,组合成双列的集合,集合中的每一组元素就是上述学习过的tuple。
|
val province = Array("山东", "河南", "陕西", "福建") val capital = Array("济南", "郑州", "西安", "福州", "桂林") //zip 拉链操作 val pcs = province.zip(capital) for((p, c) <- pcs) { println(p + "--->" + c) } |
特点:
在组合两个集合的时候,集合元素两两一一对应,如果两个集合的长度不一致,将超过的部分,或者没有匹配上的部分进行裁剪,丢弃。
二、函数式编程(上)
Java(在JDK1.8之前)是完全面向对象的编程语言,没有任何面向过程编程语言的特性,因此在Java中,一等公民是类和对象,而且只有方法的概念。Java中的方法是绝对不可能脱离类和对象独立存在的。
而Scala是一门既面向对象,又面向函数式编程的语言。因此在Scala中有非常好的面向对象的特性;而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象等一样,都是一等公民。Scala中的函数可以独立存在,不需要依赖任何类和对象。
Scala能否替代Java?
而之所以Scala一直没有替代Java,是因为Scala之前一直没有开发过太多知名的应用;而Java则不一样,Java诞生的非常早,上个世界90年代就诞生了,基于Java开发了大量知名的工程。而且最重要的一点在于,Java现在不只是一门编程语言,还是一个庞大的,涵盖了软件开发,甚至大数据、云计算的技术生态,Java生态中的重要框架和系统就太多了:Spring、Lucene、Activiti、Hadoop等等。
Java的最强大之处在于它的生态,经历了很多大型项目的洗礼!!!
(一)函数
1、函数的另一种定义
除了前面学习过的使用def关键定义函数以外,我们还可以使用Function object来定义Scala中的函数,而这个函数就是我们下面学习的函数式编程中提到的函数了。
在这种表述中,函数是一个对象,继承自FunctionN(N,代表该函数有N个参数,最多22个参数),函数对象有apply、curried、toString、tupled方法,方法则没有。
通用的定义格式为:
val 函数名 = (参数名:参数类型) => {函数体}
或者
val 函数名: (参数类型) =>返回值类型 => {函数体}
比如:
object FunctionDemo {
//通用的定义格式
val f1 = (x: Int, y: Int) => {
x + y
}
//先定义函数的参数列表类型,具体的函数参数在函数体中定义
val f2: (Int, Int, Int) => Int = {
(x, y, z) => {
x + y + z
}
}
def main(args: Array[String]): Unit = {
val v1 = f1(1, 2)
println(v1)
val v2 = f2(1, 2, 4)
println(v2)
}
}
2、函数实战
(1)作为值的函数
在Scala中,函数是头等公民,就和数一样,可以在变量中存放函数。
在Scala中,有一个约定,就是将函数赋值给变量时,必须在函数后面加上空格和下划线。
def main(args: Array[String]): Unit = {
def funcOps1(): Unit = {
def sayBye(name:String): Unit = {
println("say bye bye to " + name)
}
//函数是一等公民,可以赋值给变量
val sayGoodBye = sayBye _
//函数可以赋值给方法
def sgb = sayBye _
sayGoodBye("oldli")
sgb("oldli")
}
//调用方法
funcOps1()}
结果:
say bye bye to oldli
say bye bye to oldli
(2)、匿名函数
没有名字的函数,就是匿名函数,将参数列表和函数体使用"=>"连接起来,其作用是对参数列表中的参数,基于函数体进行操作,并类型推断其返回值。
其实匿名函数又有点类似上述的作为值的函数。
//匿名函数,没有函数签名(函数名)的函数
def funcOps2(): Unit = {
val sgb = (name:String) => {
println("say bye bye to " + name)
}
sgb("tom")
}
(3)、高阶函数
说的就是,一个由参数的函数,其参数是一个函数,函数在其参数列表中嵌套函数,或者使用函数作为输出结果,把这种函数我们称之为高阶(high level)函数。
object TestDemo7 {
//定义了一个匿名函数f1 入参还是Int类型 出参也是Int类型
var f1 = (x:Int) => x+1
//定义了一个匿名函数f2,入参是(一个函数f1),返回值是函数体内的最后一个表达式
//f1是参数,它是个函数:Int是入参类型 => Int是出参类型
//f1:Int => Int
var f2 = (f1:Int=>Int)=>{
f1(1)+123
}
//定义了一个匿名函数f3,入参是(一个函数,一个Int类型的值)
//f1是第一个参数,它是个函数:Int是入参类型 => Int是出参类型
//f1:Int => Int
//y是第二个参数,它的类型是Int
val f3 = (f1:Int => Int,y:Int) => {
//函数体f1函数有个固定的入参
// f1(1)+y
//函数 y作为了f1函数的参数
f1(y)
}
def main(args: Array[String]): Unit = {
var resutl01 = f1(1)
println(resutl01)
//f2函数中调用f1函数,f1后面不能有括号
var resutl02 = f2(f1)
println(resutl02)
var result103 = f3(f1,3)
println(result103)
}
}
这种高阶函数,其在jdk1.8以前中对应的很多方法中出现的接口类型,需要传递一个匿名内部类。在jdk1.8以后,也就有高阶函数。
(4)、参数(类型)推断
Scala函数在编写过程中,为了进行简化书写,可以对类型进行推断,变得非常简洁明了。
//Scala中函数操作过程中的类型推断
def funcOps4(): Unit = {
val money = (x:Double) => 100 * x
println(money(10:Double))
println(money(10))
println("-------------------------")
val list = Array(1, 2, 3, 4, 5)
list.foreach((x:Int) => print(x + "t"))
println("n------^~^--------")
list.foreach((x) => print(x + "t"))//简写一,省略数据类型
println("n------^~^--------")
//如果匿名函数就只有一个参数的话,可以省略掉()
list.foreach(x => print(x + "t"))
println("n------^~^--------")
//还可以使用通配符"_"来代替这个变量x,通配符就不用再写=>指向操作
list.foreach(println(_))
println("------^~^--------")
//最简洁的书写,是连这个通配符都是省略掉
list.foreach(println)
上述四中调用方式,是等效的。
(5)常见的高阶函数
a.map
def mapOps(): Unit = {
val array = Array(6, 3, 9, 7, -2)
ret = array.map((n: Int) => n * 2)
ret = array.map(n => n * 2)
ret = array.map(_ * 2)
println(ret.mkString("[", ", ", "]"))
}
b、flatMap
val arr=Array(("A",1),("B",2),("C",3))
arr.flatMap(x=>(x._1+x._2)).foreach(println)
//输出结果为:
A
1
B
2
C
3
c、foreach
foreach(p: (A) => Unit),对集合中的每一个元素进行相关的操作。
def foreachOps: Unit = {
val list = Array(1, 2, 3, 4, 5)
list.foreach((x:Int) => print(x + "t"))
println("n------^~^--------")
list.foreach((x) => print(x + "t"))//简写一,省略数据类型
println("n------^~^--------")
//如果匿名函数就只有一个参数的话,可以省略掉()
list.foreach(x => print(x + "t"))
println("n------^~^--------")
//还可以使用通配符"_"来代替这个变量x,通配符就不用再写=>指向操作
list.foreach(println(_))
println("------^~^--------")
//最简洁的书写,是连这个通配符都是省略掉
list.foreach(println)
}
d、filter
def filterOps: Unit = {
val array = Array(6, 3, 9, 7, -2)
array.filter((n: Int) => n % 2 != 0)
array.filter((n) => n % 2 != 0)
array.filter(n => n % 2 != 0)
val ret = array.filter(_ % 2 != 0)
ret.foreach(println)
}
e、dropWhile
def dropWhileOps: Unit = {
val array = Array(6, 3, 9, 7, -2)
//从集合开始删除小于9的元素,直到大于9截止
array.dropWhile((n: Int) => n < 9).foreach(println)
}
f、partition
partition(p: A => Boolean): (集合[A], 集合[A])
对集合中的元素按照某个条件进行分区,满足条件的放到Tuple2中的第一个元素的位置上,不满足条件的放到Tuple2的第二个元素的位置上。
def partitionOps: Unit = {
val array = Array(6, 3, 9, 7, -2)
//将集合中的数据分为偶数和奇数两个部分
val (even, odd) = array.partition((n: Int) => n % 2 == 0)
println("偶数:" + even.mkString("[", ", ", "]"))
println("奇数:" + odd.mkString("[", ", ", "]"))
}
partition操作,只能一分为2,不能再分。
h、reduce
reduce((A1, A2) => A3)
就是一个聚合函数,每一次拿着上一次聚合的结果A1和集合中的一个元素A2进行聚合,聚合的结果就是A3,那么在下一次的聚合过程中,A3编程就成了A1。
def reduceOps: Unit = {
val array = 1 to 10
val ret = array.reduce((sum: Int, i: Int) => {
println(s"sum: $sum, i: $i")
sum + i
})
println("sum: " + ret)
}
i、fold
fold(zeroValue)((A1, A2) => A3)
fold和reduce的原理非常相似,唯一的区别就是初始化值的问题,可以用下面的一个操作来说明fold和reduce的关系:
求和:1 + ... + 10
reduce的计算思路:
var sum = 1
for(i <- 2 to 10) {
sum = sum + i
}
fold的计算思路:
var sum = 0
for(i <- 1 to 10) {
sum = sum + i
}
案例:
def foldOps: Unit = {
val array = 1 to 10
val ret = array.fold(0)((sum: Int, i: Int) => {
println(s"sum: $sum, i: $i")
sum + i
})
println("sum: " + ret)
}
j、groupBy
// groupBy(p: (A) => B): Map[B, 集合[A]]
def groupByOps: Unit = {
//sid name gender
val stus = Array(
"1,田志,male",
"2,李威,male",
"4,范帅,female",
"5,郭颖丽,female"
)
//按照学生的性别进行分组
val gender2Infos = stus.groupBy((line: String) => {
line.substring(line.lastIndexOf(",") + 1)
})
for((gender, stus) <- gender2Infos) {
println(s"gender: $gender, stus: ${stus.mkString("[", ", ", "]")}")
}
}
(6)、闭包
在Scala中,你可以在任何作用域内定义函数、包、类甚至是另一个函数或方法。在函数体内,你可以访问到相应作用域内的任何变量。这听上去没有什么,但是请注意,函数可以在变量不再处于作用域内时被调用。
def mulBy(factor:Double) = {(x:Double) => factor * x}
val triple = mulBy(3)
val half = mulBy(0.5)
println(triple(14) + "--->" + half(14))
1)mulBy的首次调用将参数变量factor设为3,。该变量在(x:Double)=>factor * x函数的函数体内被引用。该函数被存入triple.然后参数变量factor从运行时的栈上被弹出。
2)mulBy再次被调用,这次factor被设为0.5.该变量在(x:Double)=>factor * x函数的函数体内被引用,该函数被存入half.
闭包本质上是一个函数和其引用的变量的统一定义,它的返回值依赖于这个函数外部的一个或者多个变量。
(7)、柯里化
1、柯里化(currying)指的是将原来接受2个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数。
2、在函数调用的过程中,就变为了两个函数连续调用的形式。在Spark源码中,也有体现,所以对()()这种形式的Curring函数,一定要掌握。
以下函数接受一个参数,生成另一个接受单个参数的函数。
要计算两个数的乘机,需要调用:
def main(args: Array[String]): Unit = {
//定义方法
def hello(x:Int,y:Int)={
x+y
}
//调用方法
val sum: Int = hello(2,8)
//打印输出
println(sum)
//柯里化定义
def curry(x:Int)(y:Int)={
x+y
}
//调用柯里化
val result: Int = curry(2)(8)
//打印输出
println(result)
}



