Scala实现wordcount单词计数

单词计数:将集合中出现的相同的单词,进行计数。

基础的wordcount

        基础的wordcount中,我们只需要对包含字符串的List[String]进行单词计数即可。

        我们要对这个List进行wordcount:

val stringList: List[String] = List(
  "hello",
  "hello scala",
  "hello spark scala",
  "hello flink scala",
  "hello java",
  "darren"
)

流程分析

  • 首先,我们需要将这个stringList中每一个String元素按照空格拆分为单词数组Array[String],得到一个 List[Array[String]] ,然后将这些 List[Array[String]] 进行扁平化处理,得到拆分后的 List[String] :
//将List中字符串按照空格拆分为Array[String]数组
val stringList1: List[Array[String]] = stringList.map(string => string.split(" "))

//将stringList1进行扁平化
val stringList2: List[String] = stringList1.flatten

这两步操作可以通过flatmap进行简化,将map和flat合并到一步进行:

//将list按照空格拆分后进行扁平化
val stringList12: List[String] = stringList.flatMap(string => string.split(" "))

  • 然后我们将得到的扁平化List按照字符串聚合,形成一个 String -> List[String] 的Map映射,这个Map的元素为 单词 -> 当前单词全部出现的列表
//将stringList2依据单词本身进行聚合,得到string -> List[String]的map
val stringList3 = stringList2.groupBy(string => string)
println(stringList3)

  • 接着,我们按照得到的映射,统计每个List中的元素,形成一个 String -> Int 的Map映射,这个Map映射的元素为 单词 -> 出现次数
//将stringList3进行map统计,变成一个(字符 -> 出现次数)的Map
val countMap = stringList3.map((kv: (String, List[String])) => (kv._1, kv._2.size))
println(countMap)

  • 最后,我们将这个映射转换成 List[(String, Int)] 的集合,按照 Tuple._2 进行降序排列,取出前三个元素形成结果集合即可,这个集合的元素为 (单词, 出现次数)的二元组
//将countMap转化为List[Map],并且降序排序取出前三个元素
val wordCount: List[(String, Int)] = countMap.toList.sortWith((kv1, kv2) => kv1._2 > kv2._2).take(3)
println(wordCount)

简单wordcount函数展示

基于上述流程可以构建一个简单wordcount函数:

//简单wordcount函数
def simpleWordCount(list: List[String]): List[(String, Int)] = {
  list
    .flatMap(elem => elem.split(" "))
    //对list进行按照空格拆分 & 扁平化处理
    
    .groupBy(elem => elem)
    //按照字符串元素的聚合,形成string -> List[String]的Map
    //  比如: Map(("a" -> List["a", "a", "a"]), ("b" -> List["b", "b", "b"]))
    
    .map(kv => kv._1 -> kv._2.size)
    //按照List.size(出现次数)进行统计,形成String -> Int的Map
    //  比如:Map(("a" -> 3), ("b", -> 4))
  
    .toList.sortWith((kv1, kv2) => kv1._2 > kv2._2).take(3)
    //形成List[(String, Int)],按照出现次数进行降序排序,并且取出前三个元素,将该结果直接返回
}

 复杂的wordcount

         现在我们有一个已经进行预统计的单词集合,这个集合已经标记了每个字符串出现的次数(可以理解为分布式文本统计结果的初步聚合),我们需要对其进行进一步的单词计数,统计每一个单词总共出现的次数:

//预统计字符串出现次数元组
val stringList: List[(String, Int)] = List(
  ("hello", 1),
  ("hello scala", 2),
  ("hello spark scala", 3),
  ("hello flink scala", 2),
  ("hello java", 1),
  ("darren", 9)
)

       我们很容易想到将这个List按照字符串出现次数进行拼接,转换为普通List,然后按照简单wordcount那样进行单词计数:

//简单wordcount函数
def simpleWordCount(list: List[String]): List[(String, Int)] = {
    //对list进行按照空格拆分 & 扁平化处理
  list.flatMap(string => string.split(" ")).
    //对stringList进行按照字符串元素的聚合,形成string -> List[String]的Map
    //  比如: Map(("a" -> List["a", "a", "a"]), ("b" -> List["b", "b", "b"]))
    groupBy(string => string).
    //将groupMap按照List.size(出现次数)进行统计,形成String -> Int的Map
    //  比如:Map(("a" -> 3), ("b", -> 4))
    map(kv => kv._1 -> kv._2.size).
    //将countMap形成List[(String, Int)],按照出现次数进行降序排序,并且取出前三个元素,将该结果直接返回
    toList.sortWith((kv1, kv2) => kv1._2 > kv2._2).take(3)
}
//将预统计字符串变为普通字符串
//  即将每个字符串按出现次数进行拼接
val strings1 = stringList.map(kv => (kv._1 + " ") * kv._2)
println(strings1)
val result1 = simpleWordCount(strings1)
println(result1)

流程分析

       但是这种方式浪费了预统计处理,并没能成功利用。我们在这里提供一种基于预统计结果进行处理的wordcount:

  • 原始List的元素是 (String, Int) 的一系列Tuple2,我们可以对String进行拆分,然后根据出现次数Int,将Tuple拆分为 Array[(String, Int)] ,对其进行扁平化合并,实现按照空格拆分单词同时又能利用预统计结果:
//根据预统计对每一个字符串进行拆分,得到一个(String, Int)元组数组,然后进行扁平化
//  比如("hello scala", 2),拆分后结果为:Array(("hello", 2), ("scala", 2))
val list = stringList.flatMap(tuple => {
  val stringArr: Array[String] = tuple._1.split(" ")
  stringArr.map(elem => elem -> tuple._2)
})
println(list)

  • 然后对于新的扁平化集合,我们就可以按照Tuple._1进行分组,将相同单词的元组划分到一类,得到 String -> List[(String, Int)] 的Map,其中的 List[(String, Int)] 为当前单词以及出现次数的元组,在分布式场景中可以理解为多个文件中的单词出现情况:
//对这个元组List按照tuple._1进行分组聚合,形成一个string -> List[(String, Int)]的map
//  比如:对hello有关元组进行聚合,得到:hello -> List[("hello", 1), ("hello", 2)]
val groupList = list.groupBy(tuple => tuple._1)
println(groupList)

  • 对于这个Map,我们就可以对 List[(String, Int)] 进行求和,获取该单词的出现次数了。我们可以使用map方法,对于Map中每一个kv,将其转换成kv._2中的tuple._2的和:
//对groupList中每一组kv进行value的合并,得到String -> Int的结果,并排序,取出前三
val resultList = groupList.map(kv => {
  //统计每个单词出现的次数
  //  可以通过kv._2的List[(String, Int)]调用map,获取List中元组的第二个元素,然后进行求和
  //  即:kv._2.map(tuple => tuple._2).sum
  kv._1 -> kv._2.map(tuple => tuple._2).sum
})
println(resultList)

  1. 我们对这个处理过程进行参数说明。groupList.map(kv => ) 中的kv是分组Map的每一个元组,这个元组是 (String, List[(String, Int)]) 类型的。比如:scala -> List((scala,2), (scala,3), (scala,2))。
  2. 我们需要将kv._2 (即 List[(String, Int)]) 进行求和转换,就需要对kv._2再进行一次map操作,即:kv._2.map(tuple => tuple._2).sum 。这个操作中的tuple就是kv._2中的 (String, Int) 元组,我们对这个 kv._2 转换成只包含 Int 的List,比如:List((scala,2), (scala,3), (scala,2)) 转换成 List(2, 3, 2)。
  3. 最后我们构建 String -> Int 的映射,即:kv._1 -> kv._2.map(tuple => tuple._2).sum ,也就是 当前单词 -> 在所有文档出现次数 的映射。
  • 最后我们进行 toList 转换,构建成有序集合,然后根据 出现次数(tuple._2) 进行sortWith排序,最后 使用 take 取出前三即可:
val result = resultList.toList.sortWith((kv1, kv2) => kv1._2 > kv2._2).take(3)
println(result)

复杂wordcount函数展示

    def complexWordCount(list: List[(String, Int)]): List[(String, Int)] = {
      list
        .flatMap(tuple => tuple._1.split(" ").map(string => string -> tuple._2))
        //对每一个元组的字符串都进行拆分,得到Array[String]数组,
        // 对该数组进行map处理,将元素与元组出现次数一起,得到Array[(元组中单词, 元组中单词出现次数)],
        //  比如:("hello scala", 2) -> Array(("hello", 2), ("scala", 2))
        // 并将Array进行扁平化
        // 结果为:
        // List((hello,1), (hello,2), (scala,2), (hello,3), (spark,3),
        //    (scala,3), (hello,2), (flink,2), (scala,2), (hello,1),
        //    (java,1), (darren,9)
        //  )

        .groupBy(tuple => tuple._1)
        //按照单词进行分组,得到 单词 -> List[该单词所有出现的元组] 的Map映射
        // 结果为:
        // Map(darren -> List((darren,9)), java -> List((java,1)), flink -> List((flink,2)),
        //    spark -> List((spark,3)), scala -> List((scala,2), (scala,3), (scala,2)),
        //    hello -> List((hello,1), (hello,2), (hello,3), (hello,2), (hello,1))
        // )

        .map(kv => kv._1 -> kv._2.map(tuple => tuple._2).sum)
        //对分组Map进行转换,得到 单词 -> 出现总次数的Map映射
        //  kv即分组Map集合的键值对 单词 -> List[该单词所有出现的元组]
        //  kv._1为当前单词
        //  kv._2为List[该单词出现的所有元组]
        //  kv._2.map(tuple => tuple._2)就是将kv._2转换成只有单词出现次数的List

        .toList.sortWith((kv1, kv2) => kv1._2 > kv2._2).take(3)
        //将 单词 -> 出现总次数的Map映射构建成有序集合List,
        // 按照出现次数kv._2进行降序排列,
        // 然后取前三名
    }
    println(complexWordCount(stringList))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值