实现案例前需要熟悉scala中集合的几个高级函数:
- 映射:集合.map() 即拿到集合中元素做某些处理,返回当前集合的类型。
- 扁平化:集合.flatten() 就是提取外层集合中的内层集合中的元素,打散放入到外层集合中。
- 映射和扁平化可以写成一个函数:集合.flatMap()。
- 分组:集合.groupBy()函数:根据集合中元素的某个特征进行分组,返回值类型:Map(any -> List[any])
- 排序:一般List.sorted() 、List.sortBy()、 List.sortWith() ; List.sorted: 可以不指定参数,会按照集合中的元素进行自然排序。List.sortBy()():参数为函数类型:需传入一个元素,可以根据元素某个特征进行排序,如需按照倒序排序,可以在第二个参数列表中传入:Ordering[any].reverse。List.sortWith():参数也为函数类型:需要传入两个元素,参数的函数体需返回一个boolean类型。
- 折叠: 集合.foldLeft()()函数,下面解释。
- 简化规约:集合.reduce()函数,相比 fold()()方法少了一个初始值参数列表。
对于6. 7. 中介绍的 fold()(),reduce() 方法,可以通过下面这个合并两个map的案例,进行深入理解:
foldleft()()函数源码解析 :
override /*TraversableLike*/
def foldLeft[B](z: B)(@deprecatedName('f) op: (B, A) => B): B = {
var acc = z //累计变量acc 初始值即为第一个参数
var these = this
while (!these.isEmpty) { //序列的长度不为零
acc = op(acc, these.head) //每次取一个头元素放入传进来的函数进行处理,并将结果赋给累计变量
these = these.tail //除了头就是尾
}
acc //最终将累计变量返回
}
通过源码可以看出集合的折叠方法为多阶函数(函数柯里化)
第一个参数列表为: 初始值
第二个参数列表为: 可以理解为简化的规则:将集合的元素做何种处理,一般都是传一个lambda表达式
函数测试:
//fold(),foldLeft(),foldRight()方法测试
@Test
def test_foldMethod(): Unit = {
val list1 = List(9, 2, 5, 7)
println(list1.foldLeft(10)((acc, elem) => acc + elem)) //会处理后的结果保存在acc中
//可以简写为
println(list1.fold(10)(_ + _)) //进行累加 执行过程为:10 + 9 + 2 + 5 + 7
println(list1.fold(10)(_ - _)) //执行过程为 10 - 9 - 2 - 5 - 7
//fold()底层调用了foldLeft()这里就不进行测试了
//foldRight() 方法的执行过程与foldLeft()方法并不相同,可以理解为初始值从右边与元素进行简化处理
println(list1.foldRight(10)(_ - _)) // 9 - (2 - (5 - (7 - 10)))
}
@Test
def Test_reduceMethod(): Unit = {
//简化规约:集合.reduce()方法,相比fold()()方法少了一个初始值参数列表
val list1 = List(9, 2, 5, 7)
println(list1.reduceLeft(_ - _)) //9 - 2 - 5 - 7
println(list1.reduceRight(_ - _)) //(9 - (2 - (5 - 7)))
}
合并两个map集合
@Test
def Test_MergeMap(): Unit = {
//在工作中,我们往往有合并两个map集合的需求,即:合并相同key的value,而不是进行覆盖操作
//在scala语言中,我们可以使用集合.fold()()方法对集合进行处理
val map1 = mutable.Map("hello" -> 3, "word" -> 5)
val map2 = mutable.Map("hello" -> 4, "scala" -> 4)
//使用折叠方法: 集合.foldLeft()()方法
//将map1作为初始值,map1会先赋给accMap累计变量,为初始值
val stringToInt = map2.foldLeft(map1)((accMap, kv) => {
//先取出map2中的key/value,通过二元组取元素的方式
val key = kv._1
val value = kv._2
// 使用getOrElse()方法判断集合中是否有相同key 的键值对,该方法返回值:有key就返回value,没有就返回0
//更新accMap并返回
accMap(key) = accMap.getOrElse(key, 0) + value
accMap
})
println(stringToInt)
}
普通WordCount案例
1)需求
单词计数:将集合中出现的相同的单词,进行计数,取计数排名前三的结果
2)主要使用map()、groupBy()、 sortBy()、 take()函数
代码实现:
@Test
def simpleWordCount(): Unit = {
// 1. 简单的wordCount
val lineDatas = List(
"hello java",
"hello scala",
"hello world",
"hello spark",
"hello flink",
"hello spark flink form scala"
)
val strings = Array("hello world")
strings.map(_.split(" "))
val flatMapList=lineDatas.flatMap(_.split(" "))
//这种情况比较简单,可以直接使用映射方法,直接操作集合中的元素即可
val resultMap = flatMapList.groupBy(word => word).map(kv => (kv._1,kv._2.length))
// 提取top N
println(resultMap.toList) //List((world,1), (java,1), (flink,2), (spark,2), (scala,2), (hello,6), (form,1))
//根据value进行降序排序
println(resultMap.toList.sortBy(kv => kv._2)(Ordering[Int].reverse).take(3))
}
复杂WordCount案例
代码实现:
@Test
def Test_complexWordCount(): Unit ={
// 二、复杂版本
//数据由二元组形成的List。_2:表示这行句子出现的次数
val lineCountTupleList: List[(String, Int)] = List(
("hello", 1),
("hello world", 2),
("hello scala", 3),
("hello spark from scala", 4)
)
// 第一种做法:直接将单词和次数转化为对应次数的单词
val resultMap = lineCountTupleList
.map(elem => (elem._1 + " ") * elem._2)
.flatMap(_.split("\\s+"))
.groupBy(word => word)
.map(kv => (kv._1, kv._2.size))
.toList.sortWith(_._2 > _._2)
.take(3)
println(resultMap)
//推荐做法:
// 第二种做法:将元集合中的二元组转化为(单词,次数)的形式,在进行统计
// 使用map()函数拿到集合中的元素
val newList: List[Array[(String, Int)]] = lineCountTupleList.map(
kv => {
val wordsArr = kv._1.split("\\s+")
//对数组中元素重新组合 => Array[(String,Int)] 数据类型为二元组的数组
wordsArr.map(word => (word, kv._2)) //返回
}
)
//newList.foreach(_.foreach(print))
//扁平化List集合中的数组集合
val flattenList:List[(String, Int)] = newList.flatten
//分组:将相同单词的二元组分成
//println(flattenList.groupBy(_._1))
//统计
val resultList: List[(String, Int)] = flattenList.groupBy(_._1).map(kv => {
val value = kv._2
(kv._1, value.map(_._2).sum)
}).toList.sortBy(_._2)(Ordering[Int].reverse).take(3)
println(resultList)
}