Scala 集合操作:map、filter、fold
Scala 的集合库是其最强大的功能之一,允许开发者以声明式的方式处理数据。掌握 map(转换)、filter(筛选)和 fold(聚合)这三个核心操作,足以应对绝大多数数据处理场景。
准备工作
在开始编写代码之前,确保你的开发环境已经配置好。
- 打开 IntelliJ IDEA 或 VS Code 等集成开发环境。
- 创建一个新的 Scala Worksheet 文件(后缀名为
.sc),这样可以实时看到代码的运行结果。 - 定义一组基础数据用于后续操作。输入以下代码:
val numbers = List(1, 2, 3, 4, 5)
val words = List("scala", "java", "python", "c++")
1. map:数据的“一对一”转换
map 的作用是将集合中的每一个元素取出,应用一个函数后,生成一个新的集合。新集合的元素数量与原集合保持一致。
基础用法
目标:将 numbers 列表中的每个数字乘以 2。
- 调用
List的map方法。 - 传入一个匿名函数,参数
x代表列表中的每一个元素。 - 编写转换逻辑
x * 2。
输入以下代码:
val doubled = numbers.map(x => x * 2)
// 结果: List(2, 4, 6, 8, 10)
字符串处理
目标:将 words 列表中的所有单词转换为首字母大写。
- 使用
map遍历字符串列表。 - 调用 String 的
capitalize方法。
输入以下代码:
val capitalized = words.map(word => word.capitalize)
// 结果: List("Scala", "Java", "Python", "C++")
语法糖简写
在 Scala 中,当函数参数只出现一次时,可以使用下划线 _ 作为占位符来简化代码。
修改上面的乘法代码:
val doubledShort = numbers.map(_ * 2)
2. filter:数据的“条件筛选”
filter 的作用是根据给定的条件保留元素。如果条件返回 true,则保留该元素;否则将其丢弃。
基础筛选
目标:从 numbers 中筛选出所有的偶数。
- 调用
filter方法。 - 编写判断逻辑:
x % 2 == 0(即 x 除以 2 的余数为 0)。
输入以下代码:
val evens = numbers.filter(x => x % 2 == 0)
// 结果: List(2, 4)
字符串长度筛选
目标:从 words 中筛选出长度小于 5 的单词。
- 调用
filter方法。 - 访问字符串的
length属性进行比较。
输入以下代码:
val shortWords = words.filter(w => w.length < 5)
// 结果: List("java", "c++")
3. fold:数据的“聚合”计算
map 和 filter 都是保持集合类型的操作,而 fold 则是将集合元素“折叠”成一个值。它需要一个初始值和一个聚合函数。最常用的是 foldLeft,它从左向右遍历。
基础求和
目标:计算 numbers 列表中所有数字的总和。
- 调用
foldLeft方法。 - 设置初始值为
0(因为加法的单位元是 0)。 - 编写聚合逻辑:
(累加器, 当前元素) => 累加器 + 当前元素。
输入以下代码:
val sum = numbers.foldLeft(0)((acc, x) => acc + x)
// 过程解析:
// 初始 acc=0
// 1. acc=0, x=1 -> new acc=1
// 2. acc=1, x=2 -> new acc=3
// 3. acc=3, x=3 -> new acc=6
// ...
// 结果: 15
为了更直观地理解 foldLeft 的执行流程,可以参考以下逻辑:
字符串拼接
目标:将 words 列表拼接成一个单独的字符串,中间用逗号分隔。
- 设置初始值为空字符串
""。 - 编写逻辑:如果是累加器的第一次累加(初始值),直接加单词;否则先加逗号再加单词。
- 或者使用更简单的
mkString方法,但为了练习fold,我们手动实现。
输入以下代码:
// 使用 foldLeft 实现简易 mkString
val csvString = words.foldLeft("")((acc, word) =>
if (acc.isEmpty) word else s"$acc,$word"
)
// 结果: "scala,java,python,c++"
fold 语法糖
对于累加这种常见操作,Scala 提供了更简洁的写法。
- 使用
/:操作符代替foldLeft。
输入以下代码:
val sumShort = (0 /: numbers)(_ + _)
4. 综合实战:链式操作
在实际开发中,通常会将这三个操作组合在一起使用,形成处理流水线。
目标:计算 numbers 列表中所有“偶数”的“平方”之和。
逻辑拆解:
- 过滤出偶数:
2, 4 - 转换为平方:
4, 16 - 聚合求和:
20
输入以下代码:
val result = numbers
.filter(x => x % 2 == 0) // 第一步:筛选偶数
.map(x => x * x) // 第二步:计算平方
.foldLeft(0)(_ + _) // 第三步:求和
// 结果: 20 (即 2*2 + 4*4 = 4 + 16)
为了清晰地展示这三个方法的区别,请参考下表:
| 方法名 | 核心作用 | 输入元素类型 | 输出结果类型 | 典型场景 |
|---|---|---|---|---|
map |
转换(变形) | A |
List[B] |
数据类型转换、属性提取 |
filter |
筛选(保留) | A |
List[A] |
去除无效数据、条件查询 |
fold |
聚合(归约) | A |
B (通常是单个值) |
求和、求积、拼接字符串 |
5. 高阶技巧:使用模式匹配
在 map 或 filter 中,可以结合 Scala 强大的模式匹配来处理复杂结构。
场景:假设有一个包含“年龄”和“姓名”的元组列表,找出所有成年人(年龄 >= 18),并提取他们的名字。
- 定义数据源:
val people = List((12, "Alice"), (20, "Bob"), (15, "Charlie"), (30, "David"))
- 执行链式操作:
val adultsNames = people
.filter { case (age, _) => age >= 18 } // 使用模式匹配解构元组
.map { case (_, name) => name } // 只提取 name 部分
// 结果: List("Bob", "David")
这里利用了 case 语句自动解构元组,无需手动通过 _1 和 _2 访问元素,代码可读性更高。

暂无评论,快来抢沙发吧!