当前位置: 首页 > article >正文

Scala:统计每个单词出现的个数并打印

目录

问题拆解:

解题步骤:

1. 拆分所有字符串为单词

2. 遍历所有单词并统计

3. 打印结果

基础版代码实现:

代码解释:

输出结果:

为什么这样设计?

继承的APP是个啥? 

使用高阶函数式编程

第一步:理解基础操作

目标:

第二步:拆分字符串

第三步:展平列表(flatMap)

第四步:分组统计(groupBy)

第五步:统计每组数量

第六步:排序并打印

完整代码(针对原题)

关键概念解释

1. flatMap

2. groupBy

3. mapValues

执行结果

新手常见疑问

Q1:为什么要用 split(" +") 而不是 split(" ")?

Q2:identity 是什么?

Q3:sortBy(_._1) 中的 _._1 是什么意思?

调试技巧


问题拆解:

  1. 目标:统计每个单词出现的次数。
  2. 输入:一个包含多个字符串的列表,每个字符串中有多个单词。
  3. 输出:每个单词及其出现次数,例如 Hadoop: 2

解题步骤:

1. 拆分所有字符串为单词
  • 每个字符串(如 "Hadoop Spark")需要拆分成独立的单词("Hadoop" 和 "Spark")。
  • 使用 split("\\s+") 方法按空格分割字符串(\\s+ 表示匹配一个或多个空格)。
2. 遍历所有单词并统计
  • 创建一个可变的 Map 来存储单词和对应的出现次数。
  • 遍历每个单词:
    • 如果单词已经在 Map 中,将次数 +1。
    • 如果不在 Map 中,添加该单词并将次数设为 1。
3. 打印结果
  • 遍历 Map 中的所有键值对,按格式输出。

基础版代码实现:

object WordCountBasic extends App {val list1 = List("Hadoop Spark","Spark Scala","Scala Java","Scala Akka","Hadoop Java Scala")// 步骤1:创建一个可变的Map来存储结果val wordCount = scala.collection.mutable.HashMap[String, Int]()// 步骤2:遍历每个字符串for (sentence <- list1) {// 拆分当前字符串为单词数组val words = sentence.split("\\s+")// 遍历每个单词for (word <- words) {// 如果Map中已有该单词,次数+1;否则添加单词,次数设为1if (wordCount.contains(word)) {wordCount(word) += 1} else {wordCount(word) = 1}}}// 步骤3:打印结果for ((word, count) <- wordCount) {println(s"$word: $count")}
}

代码解释:

  1. 可变Map:用 mutable.HashMap 存储单词计数,方便修改值。
  2. 双层循环
    • 外层循环 for (sentence <- list1):遍历列表中的每个字符串。
    • 内层循环 for (word <- words):遍历每个字符串拆分后的单词。
  3. 统计逻辑
    • if (wordCount.contains(word)):检查单词是否已存在。
    • wordCount(word) += 1:存在则次数+1。
    • wordCount(word) = 1:不存在则初始化为1。
  4. 最终输出:遍历 Map 并打印每个键值对。

输出结果:

复制代码

Hadoop: 2
Spark: 2
Scala: 4
Java: 2
Akka: 1

为什么这样设计?

  • 新手友好:用基础的 for 循环和 if-else 代替高阶函数,逻辑更直观。
  • 可变性:使用可变 Map 可以逐步更新状态,符合新手对“变量”的直觉。
  • 分步拆解:明确的三步走(拆分、统计、打印),降低理解难度。

object WordCountBasic extends App

继承的APP是个啥? 

在Scala中,App 是一个特质(trait)。

  1. 简化主方法
    • 当一个Scala类扩展了 App 特质时,它就不需要显式地定义 main 方法了。例如在你给出的 WordCountBasic 类扩展了 App,就可以直接在类体中编写可执行的代码,就好像这些代码是写在 main 方法内部一样。
  2. 执行入口
    • 当运行这个类时,Scala运行时会查找这个类中的可执行代码(类似于查找传统的 main 方法)并执行它。这是一种更简洁的方式来编写Scala程序的入口点,相比于定义一个包含 main 方法的类。

当一个类扩展了 App 特质时,可以省略 def main(args: Array[String]): Unit = {} 这种传统的定义主方法的形式。

  1. 原理
    • 当类扩展 App 时,Scala编译器会在背后生成一个合适的 main 方法。这个生成的 main 方法会执行类体中的代码,就好像这些代码是写在传统的 main 方法内部一样。
  2. 示例对比
    • 传统方式:
      • 如果不使用 App,你需要像这样定义一个类:
      object WordCountTraditional {def main(args: Array[String]): Unit = {// 这里编写主逻辑,比如println("Hello, world!")}
      }
    • 使用 App 方式:
      object WordCountApp extends App {println("Hello, world!")
      }
    • 在这两个示例中,WordCountApp 以更简洁的方式实现了与 WordCountTraditional 类似的功能,不需要显式地定义 main 方法。

使用高阶函数式编程

object WordCount {def main(args: Array[String]): Unit = {val list1 = List("Hadoop Spark","Spark Scala","Scala Java", "Scala Akka","Hadoop Java Scala")val wordCounts = list1.flatMap(_.split(" +"))        // 步骤1+2:拆分并展平.groupBy(identity)             // 步骤3:按单词分组.mapValues(_.size)             // 步骤3:统计每组的数量.toList                        // 转换为列表.sortBy(_._1)                  // 步骤4:按单词排序wordCounts.foreach { case (word, count) =>println(s"$word: $count")      // 步骤4:打印结果}}
}

第一步:理解基础操作

假设我们有一个简单的列表:

val simpleList = List("Hello World", "Hello Scala")
目标

统计每个单词出现的次数,结果应该是:

复制代码

Hello → 2
World → 1
Scala → 1

第二步:拆分字符串

每个句子需要拆分成单词:

// 拆分第一个元素 "Hello World" → Array("Hello", "World")
val split1 = simpleList(0).split(" ")  // 按空格拆分
println(split1.mkString(", "))  // 输出: Hello, World

问题:如果直接对整个列表用 map,会得到嵌套结构:

val splitAll = simpleList.map(_.split(" ")) 
// 结果:List(Array("Hello", "World"), Array("Hello", "Scala"))

第三步:展平列表(flatMap

用 flatMap 把嵌套的数组变成“平”的列表:

val allWords = simpleList.flatMap(_.split(" "))
// 结果:List("Hello", "World", "Hello", "Scala")

为什么用 flatMap

  • map 的结果:List(Array(...), Array(...))(两层结构)
  • flatMap 的结果:List("Hello", "World", "Hello", "Scala")(一层结构)

第四步:分组统计(groupBy

把相同的单词分到同一组:

val groups = allWords.groupBy(word => word)
// 结果:
// Map(
//   "Hello" -> List("Hello", "Hello"),
//   "World" -> List("World"),
//   "Scala" -> List("Scala")
// )

解释

  • groupBy(word => word) 表示按单词本身分组
  • word => word 可以简写为 identity(等价函数)

第五步:统计每组数量

对每个分组计算元素个数:

val counts = groups.map { case (word, list) => (word, list.size) 
}
// 结果:Map("Hello" -> 2, "World" -> 1, "Scala" -> 1)

第六步:排序并打印

将结果按字母顺序排序:

val sorted = counts.toList.sortBy(_._1) // 按单词排序
sorted.foreach { case (word, count) =>println(s"$word: $count")
}

完整代码(针对原题)

现在将上述步骤应用到原题的数据:

scala复制代码

object WordCount {def main(args: Array[String]): Unit = {val list1 = List("Hadoop Spark","Spark Scala","Scala Java", "Scala Akka","Hadoop Java Scala")// 步骤1+2:拆分并展平所有单词val allWords = list1.flatMap(_.split(" +")) // " +" 表示1个或多个空格// 步骤3:分组val groups = allWords.groupBy(identity) // 按单词分组// 步骤4:统计数量val counts = groups.mapValues(_.size) // 计算每组的长度// 步骤5:排序并打印counts.toList          // 转换为List.sortBy(_._1)        // 按单词排序.foreach { case (word, count) => println(s"$word: $count") }}
}

关键概念解释

1. flatMap
  • 作用:先做 map(转换),再 flatten(展平)
  • 示例
    List("a b", "c").flatMap(_.split(" ")) → List("a", "b", "c")
2. groupBy
  • 作用:按规则分组,返回 Map[Key, List[Value]]
  • 示例
    List("a", "b", "a").groupBy(identity) → Map("a" -> List("a", "a"), "b" -> List("b"))
3. mapValues
  • 作用:对 Map 中的值做转换,保留键不变
  • 示例
    Map("a" -> List(1,2)).mapValues(_.size) → Map("a" -> 2)

执行结果

运行代码后输出:

Akka: 1
Hadoop: 2
Java: 2
Scala: 4
Spark: 2

新手常见疑问

Q1:为什么要用 split(" +") 而不是 split(" ")
  • split(" ") 会把连续空格拆分成空字符串,例如 "a b" → Array("a", "", "b")
  • split(" +") 中的 + 表示匹配一个或多个空格,能正确处理连续空格
Q2:identity 是什么?
  • identity 是一个预定义的函数,等价于 x => x
  • 例如:groupBy(identity) 和 groupBy(x => x) 完全一样
Q3:sortBy(_._1) 中的 _._1 是什么意思?
  • _ 表示元组,例如 ("Scala", 4)
  • _._1 表示取元组的第一个元素(即单词),_._2 是第二个元素(即计数)

调试技巧

如果中间步骤不理解,可以插入 println 查看数据:

val allWords = list1.flatMap(_.split(" +"))
println(s"拆分后的单词:$allWords")  // 查看展平后的结果

另一种写法(大差不差)

相关文章:

Scala:统计每个单词出现的个数并打印

目录 问题拆解&#xff1a; 解题步骤&#xff1a; 1. 拆分所有字符串为单词 2. 遍历所有单词并统计 3. 打印结果 基础版代码实现&#xff1a; 代码解释&#xff1a; 输出结果&#xff1a; 为什么这样设计&#xff1f; 继承的APP是个啥&#xff1f; 使用高阶函数式编…...

C++前缀和

个人主页&#xff1a;[PingdiGuo_guo] 收录专栏&#xff1a;[C干货专栏] 大家好&#xff0c;今天我们来了解一下C的一个重要概念&#xff1a;前缀和 目录 1.什么是前缀和 2.前缀和的用法 1.前缀和的定义 2.预处理前缀和数组 3.查询区间和 4.数组中某个区间的和是否为特定…...

图解MOE大模型的7个核心问题并探讨DeepSeekMoE的专家机制创新

原文地址:https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-mixture-of-experts #mermaid-svg-FU7YUSIfuXO6EVHa {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FU7YUSIfuXO6EVHa .error-icon{fill…...

SpringBoot项目集成ElasticSearch

1. 项目背景 处于失业找工作的阶段&#xff0c;随便写写吧~ 没啥背景&#xff0c;没啥意义&#xff0c;Java后端越来越卷了。第一学历不是本科&#xff0c;感觉真的是没有一点路可走。 如果有路过的小伙伴&#xff0c;如果身边还有坑位&#xff0c;不限第一学历的话&#xff0…...

DeepSeek大模型深度解析:架构、技术与应用全景

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。https://www.captainbed.cn/north 文章目录 一、大模型时代与DeepSeek的定位1.1 大模型发展历程回顾大模型发展历程时间轴&#xff08;20…...

SQL AND OR 操作符详解

SQL AND & OR 操作符详解 在SQL(结构化查询语言)中,AND 和 OR 是两种非常重要的逻辑操作符,它们用于在查询条件中组合多个条件。理解并正确使用这些操作符对于编写有效的SQL查询至关重要。 引言 在处理数据库查询时,我们常常需要根据多个条件来筛选数据。AND 和 OR…...

Dubbo+Zookeeper

Apache ZooKeeper 通过当前页面下载Zookeeper 在这里启动zookeeper 可以根据这个页面简单学习一下&#xff0c;但是没有集成mysql&#xff0c;也会出现一些报错&#xff0c;且在这之后我们要使用的管理页面是vue的dubbo-admin dubbo学习三&#xff1a;springboot整合dubbozo…...

从源到目标:深度学习中的迁移学习与领域自适应实践

引言&#xff1a;数据驱动的智能时代与迁移挑战 在深度学习快速发展的今天&#xff0c;模型训练对数据量和质量的依赖成为核心瓶颈。面对新场景时&#xff0c;标注数据不足、数据分布差异等问题常导致模型性能骤降。迁移学习&#xff08;Transfer Learning&#xff09;与领域自…...

从厨电模范到数字先锋,看永洪科技如何助力方太集团开启数字新征程

在数字化洪流席卷全球的宏大背景下&#xff0c;企业转型升级的紧迫性与重要性日益凸显&#xff0c;成为驱动行业进步的关键引擎。在这一波澜壮阔的转型浪潮中&#xff0c;方太集团——厨电领域的璀璨明珠&#xff0c;以其前瞻性的战略视野和不懈的创新精神&#xff0c;携手数据…...

Redis大key

Redis大key基本概念&#xff0c;影响 Redis 大 key 指在 Redis 中存储了大量数据的键&#xff0c;它会对 Redis 的性能和内存管理产生影响。 大key的定义与value的大小和元素数量有关&#xff0c;但这个定义并不是绝对的&#xff0c;而是相对的&#xff0c;具体取决于系统的使用…...

在 Apache Tomcat 中,部署和删除项目

在 Apache Tomcat 中&#xff0c;部署和删除 WAR 文件是常见的操作。以下是详细步骤&#xff1a; 1. 删除 WAR 文件 (1) 停止应用 进入 Tomcat 的管理界面&#xff08;默认地址&#xff1a;http://localhost:8080/manager/html&#xff09;。 找到需要删除的应用&#xff0c;…...

前端基础之组件自定义事件

我们可以通过使用给组件绑定事件&#xff0c;当组件触发该事件时&#xff0c;就能进行值得返回 我们可以使用v-on属性来给子组件绑定自定义事件&#xff0c;此时该事件就会存在vc中&#xff0c;然后通过this.$emit来触发绑定的事件&#xff0c; 这样就能实现不需要app.vue来给子…...

在 Docker 中,无法直接将外部多个端口映射到容器内部的同一个端口

Docker 的端口映射是一对一的&#xff0c;即一个外部端口只能映射到容器内部的一个端口。 1. 为什么不能多对一映射&#xff1f; 端口冲突&#xff1a; 如果外部多个端口映射到容器内部的同一个端口&#xff0c;Docker 无法区分外部请求应该转发到哪个内部端口&#xff0c;会…...

基于DeepSeek(本地部署)和RAGFlow构建个人知识库

总结自视频&#xff08;很强的小姐姐视频&#xff0c;讲解清晰明了&#xff09;&#xff1a;【知识科普】【纯本地化搭建】【不本地也行】DeepSeek RAGFlow 构建个人知识库_哔哩哔哩_bilibili 1. 背景 deepseek官方网页版也虽然很强&#xff0c;能够满足绝大部分需求&#xf…...

学习工具的一天之(burp)

第一呢一定是先下载 【Java环境】&#xff1a;Java Downloads | Oracle 下来是burp的下载 Download Burp Suite Community Edition - PortSwigger 【下载方法二】关注的一个博主 【BurpSuite 安装激活使用详细上手教程 web安全测试工具】https://www.bilibili.com/video/BV…...

2025-03-05 学习记录--C/C++-PTA 习题5-8 空心的数字金字塔

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 二、解题步骤 ⭐️ 下面以n5举例&#xff1a;&#x1f98b; 第1步 &#x1f380;、外层循环&#xff08;从1到…...

C++课程设计【宿舍管理查询软件】

宿舍管理查询软件 一、题目描述二、源码以及说明宿舍管理查询软件设计与实现1. 系统设计思路1.1 功能需求1.2 数据结构2. 系统实现3. 代码说明3.1 数据结构3.2 功能实现3.3 文件存储4. 示例运行输入输出5. 总结其他QT文章推荐一、题目描述 (一)问题描述 为宿舍管理人员编写一…...

Stable Diffusion模型采样方法与参数配置详解(含步数及画风适配表)

Stable Diffusion模型采样方法与参数配置详解&#xff08;含步数及画风适配表&#xff09; 以下为当前主流采样方法的性能对比及参数配置建议&#xff0c;结合显存占用、生成速度、适用场景等维度分类总结&#xff1a; 一、采样方法对比表 采样方法推荐步数显存占用生成速度…...

极狐GitLab 17.9 正式发布,40+ DevSecOps 重点功能解读【三】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…...

vue+neo4j 四大名著知识图谱问答系统

编号: D039 视频 vueneo4j四大名著知识图谱问答系统 技术架构 vuedjangoneo4jmysql技术实现 功能模块图 问答&#xff1a;基于知识图谱检索、支持图多跳、显示推理路径 姜维的师傅的主公的臣是谁&#xff1a; 马谡 知识图谱&#xff1a;四大名著总共4个图谱 红楼梦图谱 …...

【智能体架构:Agent】LangChain智能体类型ReAct、Self-ASK的区别

1. 什么是智能体 将大语言模型作为一个推理引擎。给定一个任务&#xff0c; 智能体自动生成完成任务所需步骤&#xff0c; 执行相应动作&#xff08;例如选择并调用工具&#xff09;&#xff0c; 直到任务完成。 2. 先定义工具&#xff1a;Tools 可以是一个函数或三方 API也…...

基于eRDMA实测DeepSeek开源的3FS

DeepSeek昨天开源了3FS分布式文件系统, 通过180个存储节点提供了 6.6TiB/s的存储性能, 全面支持大模型的训练和推理的KVCache转存以及向量数据库等能力, 每个客户端节点支持40GB/s峰值吞吐用于KVCache查找. 发布后, 我们在阿里云ECS上进行了快速的复现, 并进行了性能测试, ECS…...

Vue的简单入门 三

目录 侦听器 watch 注意 表单输入绑定 v-model v-model修饰符​编辑 lazy number Trim 模板引用 组件组成 组件引用三步走 组件的嵌套关系 header Main Aside Aritice Item App.vue组件引入三个子组件 组件的注册方式 全局注册组件的方法 (1) Vue 2 语…...

指纹细节提取(Matlab实现)

指纹细节提取概述指纹作为人体生物特征识别领域中应用最为广泛的特征之一&#xff0c;具有独特性、稳定性和便利性。指纹细节特征对于指纹识别的准确性和可靠性起着关键作用。指纹细节提取&#xff0c;即从指纹图像中精确地提取出能够表征指纹唯一性的关键特征点&#xff0c;是…...

使用wifi连接手机adb进行调试|不使用数据线adb调试手机|找应用错误日志和操作日志

手机在开发者选项里要开启无线调试 在手机设置中查看WiFi的IP地址 设置 -> WLAN -> 已连接的WiFi -> IP地址 使用手机的IP地址连接 adb connect 192.168.1.12:xxxxx 检查连接状态 adb devices 断开特定设备 adb disconnect 192.168.x.x:xxxxx 断开所有设备 …...

STM32——串口通信 UART

一、基础配置 Universal Asynchronous Receiver Transmitter 异步&#xff0c;串行&#xff0c;全双工 TTL电平 &#xff1a;高电平1 低电平0 帧格式&#xff1a; 起始位1bit 数据位8bit 校验位1bit 终止位1bit NVIC Settings一栏使能接受中断。 之前有设置LCD&#xff0c;…...

PHP fastadmin 学习

安装php环境安装mysql插件 修改 php.ini下载 phpstudy、fastadmin 错误 安装FastAdmin could not find driver 参考链接 安装插件 创建1.php <? phpinfo(); ?>运行 http://127.0.0.1/1.php 查看 POD 页面访问404 伪静态 Apache <IfModule mod_rewrite.c> O…...

Autojs无线连接vscode方法

1.获得电脑的IP 在电脑的CMD界面输入 ipconfig 然后找到ipv4的那一行&#xff0c;后面的即是你的电脑IP地址 2.打开vscode的autojs服务 安装autojs插件 在vscode界面按下ctrlshiftp 输入autojs 找到 点击 之后打开手机上的autojs 之后输入刚刚电脑上的地址 可以看到vsc…...

面试基础--MySQL SQL 优化深度解析

MySQL SQL 优化深度解析&#xff1a;EXPLAIN、索引优化与分库分表实践 引言 在互联网大厂的高并发场景下&#xff0c;数据库的性能优化是至关重要的。MySQL 作为最流行的关系型数据库之一&#xff0c;SQL 查询的性能直接影响了系统的响应时间和吞吐量。本文将深入探讨 MySQL …...

python之爬虫入门实例

链家二手房数据抓取与Excel存储 目录 开发环境准备爬虫流程分析核心代码实现关键命令详解进阶优化方案注意事项与扩展 一、开发环境准备 1.1 必要组件安装 # 安装核心库 pip install requests beautifulsoup4 openpyxl pandas# 各库作用说明&#xff1a; - requests&#x…...