前言
在开发Spark的过程中,最重要的部分就是对集合的操作,这也是在学习Spark中发现对这里知识不足的发现,所以学完Scala好长时间现在又返回来重新学习Scala集合的常用方法和函数操作。这部分学完,基本已经可以熟练使用Scala开发Spark了。
foreach
foreach 是一种没有返回值 的方法,需要传入一个函数来使用,即foreach负责遍历集合,而其中的函数用来对数据进行处理,我们一般会使用匿名函数 来当做参数来使用,比较方便。遍历集合(包括字符串也是一种可迭代的集合)。
下面是cmd控制台的使用案例:
Scala
scala> arr.foreach(subarr=>{subarr.foreach(println)})
11
22
33
88
99
scala> val str="hello scala"
str: String = hello scala
scala> str.foreach(println)
h
e
l
l
o
s
c
a
l
a
之后使用foreach的场景要比for循环多得多,建议使用foreach取代for循环。
map操作
map操作是针对集合的典型变换操作,它将某个函数应用到集合中的每个元素,并产生一个结果集合;map方法返回一个与原集合类型大小都相同的新集合,只不过元素的类型可能不同。
使用方法
同样传入一个函数作为参数 ,通常是匿名函数作参数,同样会对集合进行遍历 ,相当于一个升级版的foreach ,但与 foreach 不同的是,map() 是有返回值的:
Scala
scala> val arr=Array(1,2,3,4,5,6)
arr: Array[Int] = Array(1, 2, 3, 4, 5, 6)
scala> arr.map((num:Int)=>{num*2})
res7: Array[Int] = Array(2, 4, 6, 8, 10, 12)
scala> val res = arr.map(num=>{num*2})
res: Array[Int] = Array(2, 4, 6, 8, 10, 12)
scala> res
res10: Array[Int] = Array(2, 4, 6, 8, 10, 12)
练习
给出两名学生以及他们的三科成绩(用元组表示),要求求出每名学生的最高成绩(返回一个元组)
Scala
scala> val arr = Array(("zs",Array(100,111,120)),("ls",Array(99,58,120)))
arr: Array[(String, Array[Int])] = Array((zs,Array(100, 111, 120)), (ls,Array(99, 58, 120)))
scala> arr.map(people=>{
| (people._1,people._2.max)})
res1: Array[(String, Int)] = Array((zs,120), (ls,120))
要求2:计算出两名学生优秀的成绩有几个(分数>=90)。
Scala
scala> arr.map(student=>(student._1,student._2.map(score=>if(score>=90) 1 else 0).sum))
res3: Array[(String, Int)] = Array((zs,3), (ls,2))
其中 if-else语句有点长,我们可以简写为 score >= 90 ,因为Scala 中的if-else score 语句是默认有返回值的,而 filter 同样要求返回 true 或 false 来判断是否过滤,所以我们这里可以直接简写为 score>=90 ,这就是一个布尔值。
要求3:计算出两名学生的平均值。
Scala
scala> arr.map(stu=>(stu._1,stu._2.sum * 1 / stu._2.size))
res6: Array[(String, Int)] = Array((zs,110), (ls,92))
filter
上面在使用map操作的过程中,我们会发现,map操作虽然可以有返回值,但是它把我们需要的和不需要的都会返回给我们,显然不能满足我们的使用需求,这就需要使用过滤了。
使用方法
遍历一个集合并从中获取满足条件的元素组成一个新的集合。,返回 true 代表留下,返回false 代表过滤掉。
案例
过滤数组中的奇数,留下偶数。
Scala
scala> arr.filter(num=>if(num%2==0) true else false)
res9: Array[Int] = Array(2, 4)
学生成绩大于90分的科目数量:
Scala
scala> val arr = Array(("zs",Array(100,111,120)),("ls",Array(99,58,120)))
arr: Array[(String, Array[Int])] = Array((zs,Array(100, 111, 120)), (ls,Array(99, 58, 120)))
scala> arr.map(stu=>(stu._1,stu._2.filter(score=> score>=90).size))
res10: Array[(String, Int)] = Array((zs,3), (ls,2))
flatten
扁平化的意思,适用于对多维数组(集合类型)的压缩合并,注意使用的时候没有括号。
引入
将数组中的所有元素*2:
Scala
//一维数组
scala> val arr = Array(1,2,3,4,5,6)
arr: Array[Int] = Array(1, 2, 3, 4, 5, 6)
scala> arr.map(num=> num*2)
res11: Array[Int] = Array(2, 4, 6, 8, 10, 12)
//二维数组
scala> val arr1 = Array(arr,arr)
arr1: Array[Array[Int]] = Array(Array(1, 2, 3, 4, 5, 6), Array(1, 2, 3, 4, 5, 6))
scala> arr1.map(arr=> arr.map(num=> num*2))
res12: Array[Array[Int]] = Array(Array(2, 4, 6, 8, 10, 12), Array(2, 4, 6, 8, 10, 12))
所谓二维数组翻倍,就是先用map遍历第一层数组,内层又是两个数组,对于数组这种集合类型,我们可以继续使用map进行处理,这样就可以对第二层数组中的元素进行处理了。
扁平化
对于刚才的那种二维数组,我们完全可以直接扁平化,将所有元素放到一个集合中去,简化操作。
Scala
scala> arr1
res13: Array[Array[Int]] = Array(Array(1, 2, 3, 4, 5, 6), Array(1, 2, 3, 4, 5, 6))
scala> arr1.flatten
res14: Array[Int] = Array(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6)
scala> arr1.flatten.map(num=>num*2)
res15: Array[Int] = Array(2, 4, 6, 8, 10, 12, 2, 4, 6, 8, 10, 12)
同样,对于集合类型,即使内外层数据类型不一样,只要都是集合类型(除了Map集合,元组也不行)就可以进行压缩!
Scala
scala> val arr = Array(List(1,2,3),List(4,5,6))
arr: Array[List[Int]] = Array(List(1, 2, 3), List(4, 5, 6))
scala> arr.flatten
res17: Array[Int] = Array(1, 2, 3, 4, 5, 6)
flatMap
并不是 flatten + map() 而是 map() + flatten
案例
比如我们Spark下的wordcount,假设我们有一个文件,内容为两行:
Scala
hello spark
hello scala
Spark 读取进来后,它就变成了:
Scala
Array("hello spark","hello sacla")
我们进行 wordcount 的话,就需要先把它先扁平化成一个整体,但是由于这是一个一维的数组,而不是我们扁平化所要求的内层必须也是数组或集合的形式,所以要先把我们每一个字符串元素转换为数组的形式:
Scala
Array(Array("hello spark"),Array("hello scala"))
实现过程:
Scala
scala> val arr = Array("hello spark","hello sacla")
arr: Array[String] = Array(hello spark, hello sacla)
scala> arr.map(str=> str.split(" "))
res18: Array[Array[String]] = Array(Array(hello, spark), Array(hello, sacla))
scala> res18.flatten
res19: Array[String] = Array(hello, spark, hello, sacla)
元组转为数组
很多时候,我们得到的数据不规范,比如元组类型,这就需要我们将元组转为数组的形式了:
Scala
scala> val arr = Array(("1","2"),("3","4"))
arr: Array[(String, String)] = Array((1,2), (3,4))
scala> arr.map(t=>Array(t._1,t._2))
res20: Array[Array[String]] = Array(Array(1, 2), Array(3, 4))
flatMap 用法
flatMap只需要关注 map() 不需要关注 flatten ,因为只要我们的数据满足扁平化操作的要求就可以进行扁平化,更多的我们应该关注的是 map() 的过程中,我们内层元素的格式是否为数组,如果不是(是字符串或者元组),又分别应该怎么做?所以说,我们只需要关注 map 操作即可。
Scala//内层是元组 scala> arr res24: Array[(String, String)] = Array((1,2), (3,4)) scala> arr.flatMap(t=>Array(t._1,t._2)) res25: Array[String] = Array(1, 2, 3, 4) //内层是数组 scala> val arr = Array("hello spark","hello scala") arr: Array[String] = Array(hello spark, hello scala) scala> arr.flatMap(str=> str.split(" ")) res26: Array[String] = Array(hello, spark, hello, scala)
练习
Scala
val arr = Array("zs 90 100 110","ls 50 80 110")
//希望得到
zs 90
zs 100
zs 110
ls 50
ls 80
ls 110
思路
使用 flatten + 字符串的tail / head
注意:
Array 的 head 属性是单个元素
Array 的 tail 属性是一个元素集合,它代表除了Array中第一个元素外的其他元素,所以可以进行集合操作(map、flatten等)
Scala
scala> val arr = Array("zs 90 100 110","ls 50 80 110")
arr: Array[String] = Array(zs 90 100 110, ls 50 80 110)
scala> arr.map(stu=>stu.split(" "))
res27: Array[Array[String]] = Array(Array(zs, 90, 100, 110), Array(ls, 50, 80, 110))
scala> res27.map(arr=>(arr.tail.map(t=>(arr.head,t))))
res28: Array[Array[(String, String)]] = Array(Array((zs,90), (zs,100), (zs,110)), Array((ls,50), (ls,80), (ls,110)))
scala> res28.flatten
res29: Array[(String, String)] = Array((zs,90), (zs,100), (zs,110), (ls,50), (ls,80), (ls,110))
我们可以使用flatMap简化
Scala
scala> arr
res30: Array[String] = Array(zs 90 100 110, ls 50 80 110)
scala> arr.map(str=> str.split(" ")).flatMap(arr=> arr.tail.map(t=> (arr.head,t)))
res31: Array[(String, String)] = Array((zs,90), (zs,100), (zs,110), (ls,50), (ls,80), (ls,110))