Scala的Traversable集合
1. 集合
1.1 概述
很多编程语言都提供了数据结构的对应编程库,并称之为集合库(Collection Library)。Scala中也有集合库,其优点如下:
- 易于使用:使用集合库提供的20到50个左右的方法。灵活的组合运用,能解决大部分的集合问题。
- 简洁:简单的一个单词(如foreach)就可以实现一个或多个循环操作
- 安全:大多错误都在编译期被发现
- 快速:集合类型的方法实现时,都进行了调优。用户可以根据需求选择合适的集合。
- 统一:Scala的集合有严谨的继承体系,相似类型的集合拥有同样的一组方法,以及属于自己独有的方法。
1.2 分类
Scala同时支持不可变集合和可变集合,因为不可变集合可以安全地并发访问,所以它是默认使用的集合类库。在Scala中,对于几乎所有的集合类,都提供了可变和不可变两个版本,具体如下:
- 不可变集合:集合内的元素一旦初始化完成就不可再进行更改,任何对集合的改变都将生成一个新的集合(都在
scala.collection.immutable
这个包下,使用时无需手动导包) - 可变集合:集合本身可以动态变化,且提供了改变集合内元素的方法(都在
scala.collection.mutable
这个包下,使用时需手动导包)
2. Traversable
2.1 概述
Traversable是一个特质(trait),它是其他集合的父特质,它的子特质immutable.Traversable和mutable.Traversable分别是不可变集可变集合的父特质,集合中大部分通用的方法都是在这个特质中定义的。因此了解它的功能对学习其他集合类十分重要。
2.2 格式
格式一:创建空的Traversable对象
//方式一:通过empty方法实现
val t1 = Traversable.empty[Int]
//方式二:通过小括号方式实现
val t2 = Traversable[Int]()
//方式一:通过Nil实现
val t3 = Nil
格式二:创建带参数的Traversable对象
//方式一:通过toTraversable()方法实现
val t1 = List(1, 2, 3).toTraversable
//方式二:通过Traversable的伴生对象的apply()方法实现
val t2 = Traversable(1,2,3)
2.3 创建Traversable对象
需求:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.创建空的,用来存储Int类型数据的Traversable对象
val t1:Traversable[Int] = Traversable.empty[Int]
val t2:Traversable[Int] = Traversable[Int]()
val t3:Traversable[Int] = Nil
//==比较的是集合中的数据
println(t1 == t2) //true
println(t3 == t2) //true
println(t1 == t3) //true
//eq比较的是集合的地址值
println(t1 eq t2) //true
println(t3 eq t2) //true
println(t1 eq t3) //true
//2.创建Traversable集合对象,存储数字1,2,3,并将结果打印到控制台上
val t4:Traversable[Int] = List(1,2,3).toTraversable
val t5:Traversable[Int] = Set(1,2,3).toTraversable
val t6:Traversable[Int] = Traversable(11,22,33,44,55)
//打印结果 //细节:因为Traversable是一个特质,所以底层还是通过它的具体子类来实现
println(t4) //List(1, 2, 3)
println(t5) //Se(1, 2, 3)
println(t6) //List(11, 22, 33, 44, 55)
}
}
2.4 转置Traversable集合
矩阵中有一个转置的操作。Scala中,可通过transpose()方法来实现类似操作(相当于‘列变行’)。
注意:
进行转置操作时,程序会自动检测每个集合中的元素个数是否一致,如果一致,则转置成功;否则报错。
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义一个Traversable集合t1,它有三个元素,每个元素都是Traversable集合
val t1:Traversable[Traversable[Int]] = Traversable(Traversable(1,4,7), Traversable(2,5,8), Traversable(3,6,9))
//2.对t1进行转置操作
val t2:Traversable[Traversable[Int]] = t1.transpose
//打印结果
println(t2)
}
}
//List(List(1,2,3), List(4,5,6), List(7,8,9))
2.5 拼接集合
实际开发中,数据是从多渠道获取的,所以我们经常需要拼接一些数据。Scala中,我们可以通过++
来拼接数据,但这种方式会创建大量的临时集合(即:每++一次,就会创建一个新的临时集合),针对这种情况,我们用concat()
方法。该方法会预先计算出所需的集合的大小,然后生成一个集合,减少了中间无用的临时集合,所以更加有效。
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义三个Traversable集合 //底层是创建list列表
val t1 = Traversable(11,22,33)
val t2 = Traversable(44,55)
val t3 = Traversable(66,77,88,99)
//2.拼接操作
val t4 = Traversable.concat(t1,t2,t3)
//打印结果
println(t4)
}
}
//List(11,22,33,44,55,66,77,88,99)
2.6 利用偏函数筛选元素
Scala中,可以通过collect()
方法实现偏函数结合集合来使用,从而来从集合中筛选指定的数据。
格式:
def collect[B](pf:PartialFunction[A, B]):Traversable[B]
解释:
[B]
表示通过偏函数处理后,返回值的数据类型pf:PartialFunction[A, B]
表示collect()方法需要传入一个偏函数对象Traversable[B]
表示返回的具体数据的集合
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义一个Traversable集合
val t1 = (1 to 10).toTraversable //底层是Vector
val t2 = Traversable(1,2,3,4,5,6,7,8,9,10) //底层是List
//2.筛选出集合中所有的偶数
//方式一:分解版
val pf:PartialFunction[Int, Int] = {
case x if x % 2 == 0 => x
}
val t3 = t1.collect(pf)
//方式二:合并版
val t4 = t2.collect({case x if x % 2 == 0 => x})
//打印结果
println(t3) //Vector(2,4,6,8,10)
println(t4) //List(2,4,6,8,10)
}
}
2.7 计算集合元素的阶乘
假设一个Traversable[Int]集合中包含(1,2,3,4,5)五个数字,如果我们要计算每个元素的阶乘,并放到一个新的Traversable[Int]集合中,我们可以通过递归实现,但弊端是每次计算哦都需要从头开始计算。因此,我们用scan()
方法来优化,它不仅将中间的计算结果放入新的集合,并把中间结果传递给下一次的函数调用。
格式:
def scan[B](z:B)(op:(B,B) => B)
解释:
[B]
表示返回值的数据类型(z:B)
表示初始化值(op:(B,B) => B)
表示一个具体的运算函数- scan()方法等价于scanLeft()方法,还有一个相反的方法scanRight()
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义一个Traversable集合
val t1 = Traversable(1,2,3,4,5)
//2.分别获取t1集合中各个元素的阶乘值
//方式一:普通写法
val t2 = t1.scan(1)((x:Int, y:Int) => x * y)
//方式二:合并版
val t3 = t1.scan(2)(_ * _)
//3.打印结果
println(t2) //List(1,1,2,6,24,120)
println(t3) //List(2,2,4,12,48,240)
}
}
2.8 获取集合的指定元素
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义一个Traversable集合
val t1 = Traversable(1,2,3,4,5,6)
//2.获取集合中的首尾第一个元素
println(t1.head) //1 //如果集合为空,则报错
println(t2.last) //6 //如果集合为空,则报错
println(t1.headOption) //Some(1) //如果集合为空,则返回None
println(t2.lastOption) //Some(6) //如果集合为空,则返回None
//3.获取集合中第一个偶数
println(t1.find(_ % 2 == 0)) //Some(2)
//4.获取3,4,5这三个元素,并放到一个新的Traversable集合
val t2 = t1.slice(2,5) //包左不包右
println(t2) //List(3,4,5)
}
}
2.9 判断元素是否合法
如果遇到判断集合中所有的元素是否都满足指定的条件,或任意元素满足指定的条件这种需求时,就可以用forall()和exists()方法:
-
forall()
:如果集合中所有元素都满足指定的条件,则返回true,反则返回falsedef forall(p:(A) => Boolean):Boolean
-
exists()
:只要集合中任意一个元素满足指定的条件,则返回true,反则返回falsedef exists(p:(A) => Boolean):Boolean
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义一个Traversable集合
val t1 = Traversable(1,2,3,4,5,6)
//2.判断t1中的元素是否都是偶数
println(t1.forall(_ % 2 == 0)) //false
//还可以用filter()方法,但这里不推荐,因为该方法会返回一个集合对象
println(t1.filter(_ % 2 != 0).size == 0) //false
//3.判断t1中的元素是否有偶数
println(t1.exists(_ % 2 == 0)) //true
}
}
2.10 聚合函数
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义一个Traversable集合
val t1 = Traversable(1,2,3,4,5,6)
//2.统计t1中的奇数个数是否都是偶数
println(t1.count(_ % 2 != 0)) //3
//还可以用filter()方法,但这里不推荐,因为该方法会返回一个集合对象
println(t1.filter(_ % 2 != 0).size) //3
//3.获取t1中所有元素的和、乘积、最值
println(t1.sum) //21
println(t1.product) //720
println(t1.max) //6
println(t1.min) //1
}
}
2.11 集合类型转换
有时需要把Traversable集合转换成其他的集合来进行操作,需要用到toXxx()
方法 (比如toList, toSet, toArray, toSeq等)
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.定义一个Traversable集合
val t1 = Traversable(1,2,3,4,5)
//2.把t1集合转换成其他形式
println(t1.toArray) //输出语句直接打印数组对象,打印的是地址值
println(t1.toList)
println(t1.toSet)
}
}
//[I@6537cf78
//List(1,2,3,4,5)
//Set(1,2,3,4,5)
2.12 填充元素
示例:
object ClassDemo {
def main(args:Array[String]):Unit = {
//1.
println(Traversable.fill(5)("传智播客")) //List(传智播客,传智播客,传智播客,传智播客,传智播客)
//2.
println(Traversable.fill(3)(Random.nextInt(100)))
//3.
println(Traversable.fill(5, 2)("传智播客")) //List(List(传智播客,传智播客),List(传智播客,传智播客),List(传智播客,传智播客),List(传智播客,传智播客),List(传智播客,传智播客))
//4.
println(Traversable.iterate(1, 5)(_ * 10)) //List(1,10,100,1000,10000)
//5.
println(Traversable.iterate(1,21,5)) //List(1,6,11,16)
}
}
3. 案例:随机学生序列
需求:
步骤:
object ClassDemo {
//1.创建Student样例类,用来记录学生信息
case class Student(name:String, age:Int)
def main(args:Array[String]):Unit = {
//2.定义列表,记录学生的姓名信息
val names:List[String] = List("张三", "李四", "王五", "赵六", "田七")
//3.创建随机数对象r,用来实现获取一些随机值的操作
val r:Random = new Random()
//4.创建Traversable集合,包含5个随机的学生信息
val t1:Traversable[Student] = Traversable.fill(5)(new Student(names(r.nextInt(names.size)),r.nextInt(10) + 20))
//5.将Traversable集合转换成列表
val t2:List[Student] = t1.toList
//6.通过列表的sortWith()方法,按照学生的年龄降序排列
//思路一:通过sortBy()方法实现
val sortList:List[Student] = t2.sortBy(_.age).reverse
//思路二:通过sortWith()方法实现
val sortList:List[Student] = t2.sortWith(_.age > _.age)
//7.打印结果
println(sortList) //List(Student(田七,27), Student(田七,27), Student(王五,25), Student(张三,25), Student(田七,21))
}
}