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

Scala核心编程(十一)数据结构之集合操作

本章深入讲解Scala集合的高阶函数式操作,包括映射、过滤、化简、折叠、扫描等核心API,以及拉链、迭代器、流、视图、并行集合等扩展内容。


一、集合元素的映射操作

1.1 map映射操作

需求引入:将List(3,5,7)中所有元素乘以2,返回新的List(6,10,14)

传统方法(命令式)

vallist1=List(3,5,7)varlist2=List[Int]()for(item<-list1){// 遍历list2=list2:+item*2}println(list2)// List(6, 10, 14)

传统方法缺点

  • 使用了可变变量var
  • 代码冗长,需要手动维护中间集合
  • 不符合函数式编程风格

使用map函数式解决

vallist1=List(3,5,7)deff1(n1:Int):Int={2*n1}vallist2=list1.map(f1)println(list2)// List(6, 10, 14)
map函数签名解析

HashSet为例:

defmap[B](f:(A)=>B):HashSet[B]
要点说明
[B]泛型参数,表示返回集合的元素类型
f: (A) => B接收一个函数,将A类型映射为B类型
高阶函数map可以接受函数作为参数,是函数式编程的核心特征
HashSet[B]返回新的集合,原集合不变(不可变性)
高阶函数基础
objectTestHighOrderDef{defmain(args:Array[String]):Unit={valres=test(sum,6.0)println("res="+res)// res=12.0}// 高阶函数:接收函数作为参数deftest(f:Double=>Double,n1:Double)={f(n1)}defsum(d:Double):Double={d+d}}
模拟实现map机制
classMyList{varlist1=List(3,5,7)varlist2=List[Int]()defmap(f:Int=>Int):List[Int]={for(item<-list1){list2=list2:+f(item)}list2}}objectMyList{defapply():MyList=newMyList()}// 使用valmyList=MyList()valmyList2=myList.map(f1)println("myList2="+myList2)// myList2=List(6, 10, 14)
课堂练习:转大写
valnames=List("Alice","Bob","Nick")defupper(s:String):String={s.toUpperCase}valnames2=names.map(upper)println("names="+names2)// names=List(ALICE, BOB, NICK)

1.2 flatMap扁平化映射

flat即压扁、压平、扁平化。效果是将集合中的每个元素的子元素映射到某个函数并返回新的集合。

valnames=List("Alice","Bob","Nick")defupper(s:String):String={s.toUpperCase}// 注意:每个字符串也是char集合println(names.flatMap(upper))// 输出: List(A, L, I, C, E, B, O, B, N, I, C, K)

map vs flatMap对比

操作结果说明
names.map(upper)List("ALICE", "BOB", "NICK")每个元素映射为一个新元素
names.flatMap(upper)List('A','L','I','C','E'...)映射后扁平化展开子元素

二、集合元素的过滤 - filter

作用:将符合要求的数据(筛选)放置到新的集合中。

应用案例:从List("Alice", "Bob", "Nick")中筛选首字母为’A’的元素。

valnames=List("Alice","Bob","Nick")defstartA(s:String):Boolean={s.startsWith("A")}valnames2=names.filter(startA)println("names="+names2)// names=List(Alice)

三、化简(Reduce)

3.1 基本介绍

化简:将二元函数应用于集合中的元素,进行归约操作。

需求:求List(1, 20, 30, 4, 5)的和。

vallist=List(1,20,30,4,5)defsum(n1:Int,n2:Int):Int={n1+n2}valres=list.reduceLeft(sum)println("res="+res)// res=60

3.2 reduceLeft执行逻辑

reduceLeft(_ + _) 的执行过程: ((((1 + 20) + 30) + 4) + 5) = 60 步骤分解: 1. 1 + 20 = 21 2. 21 + 30 = 51 3. 51 + 4 = 55 4. 55 + 5 = 60

3.3 课堂练习

分析下面代码的输出结果

vallist=List(1,2,3,4,5)defminus(num1:Int,num2:Int):Int={num1-num2}println(list.reduceLeft(minus))// 输出: -13 ((1-2)-3)-4)-5 = -13println(list.reduceRight(minus))// 输出: 3 1-(2-(3-(4-5))) = 3println(list.reduce(minus))// 等同于reduceLeft

使用化简求最小值

vallist=List(3,4,2,7,5)valminVal=list.reduceLeft((n1,n2)=>if(n1<n2)n1elsen2)println(minVal)// 2

四、折叠(Fold)

4.1 基本介绍

fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历。

reduceLeft与foldLeft的关系

defreduceLeft[B>:A](@deprecatedName('f)op:(B,A)=>B):B=if(isEmpty)thrownewUnsupportedOperationException("empty.reduceLeft")elsetail.foldLeft[B](head)(op)

结论reduceLeft就是调用的foldLeft(head),默认从集合的head元素开始操作。

相关函数fold,foldLeft,foldRight

4.2 应用案例

vallist=List(1,2,3,4)defminus(num1:Int,num2:Int):Int={num1-num2}// foldLeft(初始值)(函数) - 柯里化写法println(list.foldLeft(5)(minus))// 5-1-2-3-4 = -5println(list.foldRight(5)(minus))// 1-(2-(3-(4-5))) = -3

4.3 缩写方法

完整写法缩写说明
list.foldLeft(1)(minus)(1 /: list)(minus)/: 左折叠
list.foldRight(10)(minus)(list :\ 10)(minus):\ 右折叠
vallist4=List(1,9,2,8)// 左折叠: 1 - 1 - 9 - 2 - 8 = -19vari6=(1/:list4)(minus)println(i6)// -19// 左折叠: 100 - 1 - 9 - 2 - 8 = 80i6=(100/:list4)(minus)println(i6)// 80// 右折叠: 1 - (9 - (2 - (8 - 10))) = -4i6=(list4:\10)(minus)println(i6)// -4

五、扫描(Scan)

5.1 基本介绍

扫描:对某个集合的所有元素做fold操作,但是会把产生的所有中间结果放置于一个集合中保存。

defminus(num1:Int,num2:Int):Int={num1-num2}// 5 (1,2,3,4,5) => (5, 4, 2, -1, -5, -10)vali8=(1to5).scanLeft(5)(minus)println(i8)// Vector(5, 4, 2, -1, -5, -10)defadd(num1:Int,num2:Int):Int={num1+num2}// 5 (1,2,3,4,5) => (5, 6, 8, 11, 15, 20)vali9=(1to5).scanLeft(5)(add)println(i9)// Vector(5, 6, 8, 11, 15, 20)

5.2 课堂练习

deftest(num1:Int,num2:Int):Int={num1*num2}vali8=(1to3).scanLeft(3)(test)println(i8)// Vector(3, 3, 6, 18)// 计算过程:// 初始值: 3// 3 * 1 = 3// 3 * 2 = 6// 6 * 3 = 18

六、集合综合应用案例

6.1 练习1:foldLeft存放到ArrayBuffer

valsentence="AAAAAAAAAABBBBBBBBBCCCCCCDDDDDDDD"defputArry(arr:ArrayBuffer[Char],c:Char):ArrayBuffer[Char]={arr.append(c)arr}valarr=ArrayBuffer[Char]()sentence.foldLeft(arr)(putArry)println(arr.mkString)// AAAAAAAAAABBBBBBBBBCCCCCCDDDDDDDD

6.2 练习2:统计字符出现次数

Java实现

Stringsentence="AAAAAAAAAABBBBBBBBBCCCCCCDDDDDDDD";Map<Character,Integer>charCountMap=newHashMap<>();char[]cs=sentence.toCharArray();for(charc:cs){if(charCountMap.containsKey(c)){Integercount=charCountMap.get(c);charCountMap.put(c,count+1);}else{charCountMap.put(c,1);}}System.out.println(charCountMap);

Scala foldLeft实现

valsentence="AAAAAAAAAABBBBBBBBBCCCCCCDD"defcharCount(map:Map[Char,Int],c:Char):Map[Char,Int]={map+(c->(map.getOrElse(c,0)+1))}valmap2=sentence.foldLeft(Map[Char,Int]())(charCount)println(map2)// Map(A -> 10, B -> 9, C -> 6, D -> 7)

6.3 练习3:大数据经典WordCount

vallines=List("atguigu han hello ","atguigu han aaa aaa aaa ccc ddd uuu")// 1. flatMap拆分单词valres1=lines.flatMap(_.split(" "))println("res1="+res1)// res1=List(atguigu, han, hello, atguigu, han, aaa, aaa, aaa, ccc, ddd, uuu)// 2. map转换为 (单词, 1) 的元组valres2=res1.map((_,1))println("res2="+res2)// 3. groupBy按单词分组valres3=res2.groupBy(_._1)println("res3="+res3)// Map(han -> List((han,1), (han,1)), atguigu -> ...)// 4. map统计每个单词出现次数valres4=res3.map(x=>(x._1,x._2.size))println("res4="+res4)// Map(han -> 2, atguigu -> 2, hello -> 1, aaa -> 3, ccc -> 1, ddd -> 1, uuu -> 1)// 5. toList后按次数排序valres5=res4.toList.sortBy(_._2)println("res5="+res5)// List((hello,1), (ccc,1), (ddd,1), (uuu,1), (han,2), (atguigu,2), (aaa,3))// 6. 从大到小排序valres6=res5.reverse println("res6="+res6)// List((aaa,3), (han,2), (atguigu,2), (hello,1), (ccc,1), (ddd,1), (uuu,1))

七、扩展内容

7.1 拉链(Zip)合并

基本介绍:将两个集合进行对偶元组合并

vallist1=List(1,2,3)vallist2=List(4,5,6)vallist3=list1.zip(list2)println("list3="+list3)// list3=List((1,4), (2,5), (3,6))

注意事项

  1. 拉链的本质是两个集合的合并操作,合并后每个元素是一个对偶元组
  2. 如果两个集合个数不对应,会造成数据丢失
  3. 集合不限于List,也可以是Array等其他集合
  4. 取出数据时按元组方式取出:
for(item<-list3){println(item._1+" "+item._2)// 1 4, 2 5, 3 6}

7.2 迭代器(Iterator)

通过iterator方法从集合获得一个迭代器,通过while循环和for表达式遍历。

valiterator=List(1,2,3,4,5).iterator// 遍历方式1:whileprintln("--------遍历方式1 ---------")while(iterator.hasNext){println(iterator.next())}// 遍历方式2:forprintln("--------遍历方式2 for ---------")for(enum<-iterator){println(enum)}

底层实现

// iterator的构建实际是AbstractIterator的一个匿名子类defiterator:Iterator[A]=newAbstractIterator[A]{varthese=selfdefhasNext:Boolean=!these.isEmptydefnext():A=...}

7.3 流 Stream

基本说明:Stream是一个集合,可以用于存放无穷多个元素,但这无穷个元素并不会一次性生产出来,而是需要用到多大的区间,就会动态的生产,末尾元素遵循lazy规则(即:要使用结果才进行计算)。

创建Stream对象

defnumsForm(n:BigInt):Stream[BigInt]=n #::numsForm(n+1)valstream1=numsForm(1)// 取出第一个元素println("head="+stream1.head)// head=1println(stream1.tail)// Stream(2, ?)println(stream1)// Stream(1, ?)

使用map映射Stream

defnumsForm(n:BigInt):Stream[BigInt]=n #::numsForm(n+1)defmulti(x:BigInt):BigInt={x*x}println(numsForm(5).map(multi))// Stream(25, ?)

⚠️注意:如果使用流集合,就不能使用last属性,否则集合会进行无限循环


7.4 视图 View

Stream的懒加载特性,也可以对其他集合应用view方法来得到类似的效果。

特点

  1. view方法产出一个总是被懒执行的集合
  2. view不会缓存数据,每次都要重新计算(比如遍历View时)

应用案例:找到1-100中,数字倒序排列和它本身相同的所有数。

defmultiple(num:Int):Int={num}defeq(i:Int):Boolean={i.toString.equals(i.toString.reverse)}// 没有使用view - 立即计算所有结果valviewSquares1=(1to100).map(multiple).filter(eq)println(viewSquares1)// 使用view - 懒执行valviewSquares2=(1to100).view.map(multiple).filter(eq)println(viewSquares2)// for (x <- viewSquares2) {} // 遍历时才真正计算

7.5 线程安全的集合

所有线程安全的集合都是以Synchronized开头的集合:

  • SynchronizedBuffer
  • SynchronizedMap
  • SynchronizedPriorityQueue
  • SynchronizedQueue
  • SynchronizedSet
  • SynchronizedStack

7.6 并行集合

Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。

主要算法

  1. Divide and conquer(分治算法):Scala通过splitters(分解器)、combiners(组合器)等抽象层来实现,将计算工作分解为很多任务,分发给处理器完成,并合并结果返回。

  2. Work stealin算法:主要用于任务调度负载均衡,通俗地说就是完成自己的所有任务后,发现其他人还有活没干完,主动帮他人一起干。

应用案例

// 串行打印(1to5).foreach(println(_))// 并行打印(顺序可能不一致)(1to5).par.foreach(println(_))// 查看并行集合中元素访问的线程valresult1=(0to100).map{case_=>Thread.currentThread.getName}valresult2=(0to100).par.map{case_=>Thread.currentThread.getName}println(result1)// 所有元素由main线程处理println(result2)// 元素由多个线程并行处理

7.7 操作符扩展

反引号

如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号:

val`val`=42// 使用保留字作为变量名
中置操作符

A 操作符 B等同于A.操作符(B)

valn1=1valn2=2valr1=n1+n2// 中置写法valr2=n1.+(n2)// 方法调用写法(等价)
后置操作符

A操作符等同于A.操作符,如果操作符定义时不带()则调用时不能加括号:

classOperate{def++="123"// 定义时省略()}valoper=newOperate println(oper++)// 正确// println(oper.++) // 正确// println(oper++()) // 错误!定义时没带(),调用不能加()
前置操作符

+-!~等操作符A等同于A.unary_操作符

classOperate{defunary_!=println("!!!!!!!")}valoper=newOperate!oper// 前置运算符,输出: !!!!!!!
赋值操作符

A 操作符= B等同于A = A 操作符 B,比如A += B等价于A = A + B


八、本章小结

操作方法核心特点
映射map/flatMap一对一映射 / 映射后扁平化
过滤filter按条件筛选元素
化简reduceLeft/reduceRight二元归约,无初始值
折叠foldLeft/foldRight二元归约,有初始值
扫描scanLeft/scanRight保留所有中间结果
拉链zip两个集合对偶合并
迭代器iterator惰性遍历集合
Stream无穷集合,懒加载
视图view懒执行,不缓存
并行.par多核并行计算

掌握这些高阶函数式操作,是写出优雅、简洁、高效Scala代码的关键!

http://www.gsyq.cn/news/1430367.html

相关文章:

  • 用 changedetection.io 监控网页变化和价格变动
  • 白话skills之二:Prompt和Skills的区别是什么?
  • 保姆级教程:用Pix4D和ArcGIS处理DJI M3M/P4M多光谱数据,从辐射标定到NDVI提取
  • 【多变量输入单步预测】基于减法优化器算法(SABO)优化CNN-BiLSTM-Attention的风电功率预测研究附Matlab代码
  • BilibiliDown:三步搞定B站视频本地化,收藏夹批量下载神器
  • Arduino步进电机旋转标志牌:从电路设计到3D打印的全流程创客实践
  • 揭秘Android启动流程的7大安全关卡
  • 2026年新国标充电宝(GB 47372-2026)MOSFET选型方案
  • 个人AI助手配置避坑清单(2024年真实压测数据版):92%用户忽略的3个延迟黑洞与5项安全断点
  • 3分钟快速上手:PicQuickCompare让图片差异检测变得前所未有的简单
  • 国产化替代实战:如何在飞腾/鲲鹏/龙芯等不同CPU上安装银河麒麟V10?
  • ICO预算规划全解析:从合规到营销的成本控制与实战策略
  • 告别命令报错:用nvm管理Node版本后,Vue CLI命令失效的修复方案
  • Scrum Meeting 09
  • SAP Cloud ERP 能不能理解成以前的 SAP S/4HANA Cloud Public Edition,一位 ABAP 开发者视角下的准确说法
  • 别再乱用reset_index了!深入理解Pandas索引机制与set_index/reset_index的黄金搭档用法
  • 儋州本地专业防水TOP5靠谱推荐:家里漏水不用愁,免费上门不求人。本地最新防水企业资讯:专业师傅持证上门,收费透明无隐藏收费,质保5-10年,售后有保障 - 企业资讯
  • 2026邯郸市防水补漏公司权威推荐:卫生间、阳台、屋顶、地下室、飘窗、外墙漏水,专业防水公司TOP5口碑榜+全维度测评(2026年6月最新深度行业资讯) - 防水百科
  • 怎么5分钟搞定碧蓝航线全皮肤:Perseus游戏增强补丁终极攻略
  • 百公里光缆怎么测?鼎讯信通 BM-S3 OTDR 性能解析
  • 108、传输极限测试方法论:眼图分析、误码率测试与链路预算模型
  • Windows 11系统性能瓶颈诊断与深度优化终极指南
  • 膜厚测试仪怎么选?资深工程师的 5 个选购标准 - 新闻快传
  • 从Vis.js到D3.js:我为什么最终选择了D3来构建企业级网络拓扑可视化?
  • 考研机构收费体系解析,附考研机构选择指南 - 新闻快传
  • 2026晋中市防水补漏公司权威推荐:卫生间、阳台、屋顶、地下室、飘窗、外墙漏水,专业防水公司TOP5口碑榜+全维度测评(2026年6月最新深度行业资讯) - 防水百科
  • 告别门禁通话杂音与回音:A-59P语音模组让智能家居对话更清晰
  • 微小面积膜厚检测难题破解:膜厚测试仪技术深度测评 - 新闻快传
  • 3个关键步骤解决Windows系统级音频处理难题:Equalizer APO完整指南
  • VINS-Fusion实战评测:不同传感器配置(单目/双目/IMU/GPS)在EUROC数据集上的EVO精度对比