Scala 集合:基础 API

Traversable 和 Iterable 特质定义了 scala 集合的基本操作,后续文章中将要介绍的 Seq、Set,以及 Map 等集合都实现了这两个特质。本文主要对 Traversable 和 Iterable 中定义的方法进行归类和介绍,了解这些方法也就基本知道了 scala 集合的大部分操作。

Traversable 定义为 Trait 类型,包含 2 个直接派生的子特质 mutable.Traversableimmutable.Traversable,分别表示可变集合和不可变集合。其中不可变集合是指集合中的元素一旦初始化完成便不可再被修改,任何对该集合的修改操作都将生成一个新的集合。Traversable 特质的定义如下:

1
2
3
4
trait Traversable[+A] extends TraversableLike[A, Traversable[A]]
with GenTraversable[A]
with TraversableOnce[A]
with GenericTraversableTemplate[A, Traversable]

Traversable 是一个 Trait 类型,所以我们不能直接通过 new 关键字来创建 Traversable 对象,但是 scala 为 Traversable 定义了伴生对象,我们可以通过伴生对象的 apply 方法创建 Traversable 类型对象(eg. Traversable(1, 2, 3))。同时我们可以使用 repr 函数得到这个具体的实现类对象:

1
2
val t = Traversable(1 until 10: _*)
t.repr // 返回的是一个 List 对象

Iterable 继承自 Traversable,也是一个特质类型,定义如下:

1
2
3
4
trait Iterable[+A] extends Traversable[A]
with GenIterable[A]
with GenericTraversableTemplate[A, Iterable]
with IterableLike[A, Iterable[A]]

Iterable 同样包含 2 个直接派生的子特质 mutable.Iterableimmutable.Iterable

一. 构造 & 填充

1.1 fill

函数 fill 可以生成指定维度的集合,并使用给定的值对集合进行填充。示例:

1
2
3
4
val t1 = Traversable.fill(3)("A")
t1 // 输出:List(A, A, A)
val t3 = Traversable.fill(2, 3, 4)(RandomUtils.nextInt(0, 100))
t3.foreach(println)

集合 t3 内容如下:

1
2
List(List(18, 43, 2, 78), List(7, 78, 52, 20), List(7, 85, 77, 85))
List(List(82, 15, 36, 29), List(5, 83, 32, 78), List(99, 22, 13, 22))

函数 fill 包含多个重载版本(如下),其中第 1 组参数用于指定目标集合的维度,第 2 个函数是 elem: => A 类型,允许我们使用不同的元素对集合进行填充,例如示例中指定的随机数函数。

1
2
3
4
5
def fill[A](n: Int)(elem: => A): CC[A]
def fill[A](n1: Int, n2: Int)(elem: => A): CC[CC[A]]
def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[CC[CC[A]]]
def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[CC[CC[CC[A]]]]
def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[CC[CC[CC[CC[A]]]]]

1.2 tabulate

函数 tabulate 与 fill 的功能类似,区别在于第 2 个函数,函数 tabulate 会将集合对应的下标值传递给函数 f,我们可以依据下标值生成集合元素值。函数 tabulate 的定义如下:

1
2
3
4
5
def tabulate[A](n: Int)(f: Int => A): CC[A]
def tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): CC[CC[A]]
def tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): CC[CC[CC[A]]]
def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): CC[CC[CC[CC[A]]]]
def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): CC[CC[CC[CC[CC[A]]]]]

示例(生成乘法口诀表):

1
2
val t = Traversable.tabulate(9, 9)((x, y) => (x + 1) * (y + 1))
t.foreach(h => println(h.mkString("\t")))

输出:

1
2
3
4
5
6
7
8
9
1	2	3	4	5	6	7	8	9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81

1.3 iterate

函数 iterate 的定义如下,其中参数 start 用于指定起始值,len 用于限定生成集合的长度,并依据 start 值应用 f 函数生成集合元素,生成算法为 start, f(start), f(f(start)), ...

1
def iterate[A](start: A, len: Int)(f: A => A): CC[A]

示例:

1
2
val t = Traversable.iterate(1, 10)(_ + 2)
t // 输出:List(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)

1.4 range

函数 range 提供了 3 个参数,其中 start 和 end 用于指定结果元素值的上下界,参数 step 用于指定步进值:

1
def range[T: Integral](start: T, end: T, step: T): CC[T]

示例:

1
2
val t = Traversable.range(0, 11, 2)
t // 输出:List(0, 2, 4, 6, 8, 10)

二. 平展操作

2.1 flatten

假设我们希望对 Traversable(Traversable(1, 2), Traversable(2, 3), Traversable(3, 4)) 执行平展操作得到 (1, 2, 2, 3, 3, 4),那么可以使用 flatten 函数实现。示例:

1
2
val t = Traversable(Traversable(1, 2), Traversable(2, 3), Traversable(3, 4))
t.flatten // 输出:List(1, 2, 2, 3, 3, 4)

关于 flatten 操作的 3 个问题:

  1. 如果 Traversable 对象中包含的元素类型不一致怎么办,平展后的集合类型是什么?

这种情况下 scala 会从类型继承树中寻找这些类型的公共父类型,并以公共类型作为结果类型,最差的结果就是生成 Traversable[Any] 类型的集合。示例:

1
2
val t = Traversable(Traversable(1, 2), Traversable(2L, 3L), Traversable("3", "4"))
t.flatten // 输出:res: Traversable[Any] = List(1, 2, 2, 3, 3, 4)
  1. 如果 Traversable 对象中包含的元素,有的是普通类型,有的是集合类型,能否平展?

如果不添加隐式转换,那么这种情况下是不允许平展的,因为 flatten 方法要求集合的元素必须继承或者能够转换成 GenTraversableOnce 类型。但是如果添加隐式转换,将元素类型转换成 Traversable 类型,就可以实现转换。示例:

1
2
3
4
5
6
7
8
// 隐式转换
implicit def asTraversable[T <: Any](x: T): Traversable[T] = x match {
case v: Traversable[T] => v
case v => Traversable(v)
}

val t = Traversable(1, 2, Traversable(2, 3), Traversable(3, 4))
t.flatten(asTraversable) // 输出:List(1, 2, 2, 3, 3, 4)
  1. 如果 Traversable 对象中包含的元素是多层嵌套集合,能否平展?

平展只能是浅层的,所以这种情况下并不会执行平展操作。

1
2
val t = Traversable(Traversable(Traversable(1, 2), Traversable(2, 3)), Traversable(Traversable(3, 4)))
t.flatten // 不会平展

平展 flatten 操作有一个比较典型的应用就是对包含 Option 类型的 Traversable 执行平展操作,剔除 None 值,返回 Some 所包含的值。示例:

1
2
val t = Traversable(Some(1), None, Some(2))
t.flatten // 输出:List(1, 2)

三. 转置操作

3.1 transpose

假设我们需要一个矩阵执行转置操作(如下),那么在 scala 中可以使用 transpose 操作完成。

1
2
3
1  4  7      1  2  3
2 5 8 -> 4 5 6
3 6 9 7 8 9

实现:

1
2
val matrix = Traversable(Traversable(1, 2, 3), Traversable(4, 5, 6), Traversable(7, 8, 9))
matrix.transpose // 输出:List(List(1, 4, 7), List(2, 5, 8), List(3, 6, 9))

注意 :每个集合中包含的元素个数必须一致。

四. (拉)拉链操作

4.1 zip

函数 zip 用于对两个 Iterable 对象执行拉拉链操作,并 以较短的集合为准,忽略较长集合中多出来的元素 ,示例:

1
2
3
val itr1 = Iterable(1, 3, 5)
val itr2 = Iterable("A", "B", "C", "D")
itr1.zip(itr2) // 输出:List((1,A), (3,B), (5,C))

4.2 zipAll

函数 zipAll 同样用于对两个 Iterable 对象执行拉拉链操作,但是与 zip 相反的是,函数 zipAll 以较长的集合为准,并用提供的默认值对较短的集合进行弥补 。示例:

1
2
3
val itr1 = Iterable(1, 3, 5)
val itr2 = Iterable("A", "B", "C", "D")
itr1.zipAll(itr2, 0, "X") // 输出:List((1,A), (3,B), (5,C), (0,D))

函数 zipAll 的定义如下:

1
def zipAll[B, A1 >: A, That](that: GenIterable[B], thisElem: A1, thatElem: B)(implicit bf: CanBuildFrom[Repr, (A1, B), That]): That

其中参数 thisElem 用于设置左边集合对应的默认值,参数 thatElem 用于设置右边集合对应的默认值。

4.3 zipWithIndex

函数 zipWithIndex 用于将 Iterable 对象中的元素与集合下标进行拉拉链操作,示例:

1
2
val itr = Iterable("A", "B", "C", "D")
itr.zipWithIndex // 输出:List((A,0), (B,1), (C,2), (D,3))

如果需要将下标放置在前面,我们可以使用 map 函数进行转换:

1
2
val itr = Iterable("A", "B", "C", "D")
itr.zipWithIndex.map(x => (x._2, x._1)) // 输出:List((0,A), (1,B), (2,C), (3,D))

五. (解)拉链操作

5.1 unzip

示例:

1
2
3
4
val t = Traversable("a" -> 1, "b" -> 2, "c" -> 3)
val tuple = t.unzip
tuple._1 // 输出:List(a, b, c)
tuple._2 // 输出:List(1, 2, 3)

函数 unzip 的定义如下:

1
def unzip[A1, A2](implicit asPair: A => (A1, A2)): (CC[A1], CC[A2])

函数接收一个 A => (A1, A2) 类型的 asPair 隐式参数,我们可以自定义该隐式参数,以实现更加复杂的解拉链操作,示例:

1
2
3
4
val t = Traversable("a_1", "b_2", "c_3")
val tuple = t.unzip(x => (x(0), x.substring(2).toInt))
tuple._1 // 输出:List(a, b, c)
tuple._2) // 输出:List(1, 2, 3)

5.2 unzip3

函数 unzip 用于将 1 个集合分成 2 个集合,而函数 unzip3 则用于将 1 个集合分成 3 个集合,unzip3 的定义如下:

1
def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (CC[A1], CC[A2], CC[A3])

函数 unzip3 接收一个 A => (A1, A2, A3) 类型的 asTriple 隐式参数。示例:

1
2
3
4
5
6
7
8
val t = Traversable("name, age, school", "zhenchao, 28, WHU", "guida, 28, HUST")
val tuple3 = t.unzip3(x => {
val elems = x.split(", ")
(elems(0), elems(1), elems(2))
})
tuple3._1 // 输出:List(name, zhenchao, guida)
tuple3._2 // 输出:List(age, 28, 28)
tuple3._3 // 输出:List(school, WHU, HUST)

六. 连接操作

6.1 ++

如果有 2 个 Traversable 对象,我们希望将这 2 个对象中的元素进行连接,可以使用 ++ 函数,示例:

1
2
3
val t1 = Traversable(1, 2, 3)
val t2 = Traversable(3, 4, 5)
t1 ++ t2 // 输出:List(1, 2, 3, 3, 4, 5)

需要注意的是,函数 ++ 并不要求这两个 Traversable 对象中的元素类型必须一致,示例:

1
2
3
val t1 = Traversable(1, 2, 3)
val t2 = Traversable("3", "4", "5")
t1 ++ t2 // 输出:List(1, 2, 3, 3, 4, 5)

连接操作的结果类型是 scala.collection.immutable.$colon$colon,即 scala.collection.immutable.::,其中 :: 类型是 List 的子类型。具体的元素类型是两个 Traversable 对象中元素类型的公共父类型,这里对应的是 Any 类型。

在 Traversable 的实现中,结果类型与左边的集合类型保持一致,示例:

1
2
3
4
val t1 = List(1, 2, 3)
val t2 = Set(3, 4, 5)
t1 ++ t2 // 结果类型为 List 类型
t2 ++ t1 // 结果类型是 Set 类型

另外 Traversable 还提供了 ++: 方法,该方法也表示连接操作,只是将左边的集合连接到右边的集合,结果类型由右边的集合决定。 在 scala 中,以 : 结尾的函数都是右操作的 ,即 A ++ B 等价于 B ++: A

Scala 允许以一些特殊符号对类或方法进行命名,但是这在 JVM 中是不允许,为了保证能够正常编译,Scala 使用 mangled 技术将这些特殊字符编码成 $name 的形式以满足 JVM 的要求。对应字符编码之后的值如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~ -> $tilde
= -> $eq
< -> $less
> -> $greater
! -> $bang
# -> $hash
% -> $percent
^ -> $up
| -> $bar
* -> $times
/ -> $div
+ -> $plus
- -> $minus
: -> $colon
\ -> $bslash
? -> $qmark
@ -> $at

6.2 concat

如果我们需要对多个 Traversable 对象执行连接操作,一种解决方式就是对所有的对象执行 ++ 操作,即 A ++ B ++ C ++ ...,但是这样会在每次执行 ++ 操作时生成一个新的集合,影响性能。

这种情况下可以使用 concat 函数,它会预先计算出所需的结果集合大小,然后生成结果,减少了中间临时集合对象的生成。示例:

1
2
val t = Traversable.concat(Traversable(0 to 5: _*), Traversable(5 to 10: _*), Traversable(10 to 15: _*))
// 输出:List(0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15)

七. 使用偏函数(PartialFunction)对结果进行收集

7.1 collect & collectFirst

函数 collect 的定义如下,它接收一个偏函数 PartialFunction 类型的参数:

1
def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That

我们可以自定义偏函数对 Traversable 集合中的元素进行筛选,仅保留满足条件的集合,示例:

1
2
3
4
5
6
def filterEven: PartialFunction[Int, String] = {
case x if x % 2 == 0 => x.toString // 输出类型为 String,仅输出偶数
}

val t = Traversable(1 to 10: _*)
t.collect(filterEven) // 输出:List(2, 4, 6, 8, 10)

与 filter 函数不同的是,偏函数接收一个集合中的元素 A,并输出一个结果元素 B,元素 B 的类型可以与 A 的类型不同。可以说 collect 函数兼具 filter 和 map 的功能。

函数 collectFirst 是 collect 的特殊版本,它返回满足条件的第 1 个元素,对应 Option 类型,如果不存在满足条件的元素则返回 None。

偏函数说明:

包括在花括号内的一组 case 语句组成一个偏函数(partial function),偏函数并非对所有输入值都有定义,常见的 try-catch 语句的 catch 子句就是一个偏函数。偏函数是特质 PartialFunction[-A, +B] 的一个实例,其中 A 是入参类型,B 是返回值类型,该类有两个方法,apply 方法从匹配到的模式计算函数值,isDefinedAt 方法校验当前的输入是否有对应匹配的模式,如果有则返回 true,否则返回 false。示例:

1
2
3
4
val f: PartialFunction[Char, Int] = {
case '+' => 1
case '-' => -1
}

调用:

1
2
3
"-3+4".collect(f) // Vector(-1, 1)
f.apply('+') // 1
f.isDefinedAt('*') // false

前面说到偏函数并非对所有的输入值都有定义,这里我们的入参为 -3+4,而偏函数 f 仅对 -+ 有定义,所以返回值是 (-1, 1)。如果完全覆盖了所有场景则是一个 Function1,而不仅仅是一个 PartialFunction。

我们可以调用 PartialFunction#lift 方法将一个偏函数转换成返回 Option[R] 类型的常规函数,这样对于偏函数有定义的输入值返回 Some 类型,没有的则返回 None。方法 unlift 可以将一个常规函数转换成一个偏函数。

八. 过滤操作

8.1 filter & filterNot

函数 filter 和 filterNot 用于对 Traversable 对象中的元素进行筛选,区别在于前者保留满足筛选条件的元素,而后者保留不满足筛选条件的元素。示例:

1
2
3
val t = Traversable(1 to 10: _*)
t.filter(_ % 2 == 0) // 输出:List(2, 4, 6, 8, 10)
t.filterNot(_ % 2 == 0) // 输出:List(1, 3, 5, 7, 9)

8.2 withFilter

函数 withFilter 同样接收一个谓词 A => Boolean,对 Traversable 对象中的元素进行筛选,并保留满足条件的元素。区别于 filter,函数 withFilter 返回的结果类型是 FilterMonadic 特质,定义如下:

1
2
3
4
5
6
trait FilterMonadic[+A, +Repr] extends Any {
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
def flatMap[B, That](f: A => scala.collection.GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That
def foreach[U](f: A => U): Unit
def withFilter(p: A => Boolean): FilterMonadic[A, Repr]
}

也就是说我们在调用 withFilter 函数之后,接下去只能调用 map、flatMap、foreach,以及 withFilter 这几个函数。这里对应函数式编程的 Monad 概念,表示一个计算序列,可以让程序使用管道式的方式处理数据。在这样的计算模式中可以流式调用多个上述函数,但是只有在调用 foreach 时才会真正执行计算。而 filter 函数在每次调用时都会进行计算并返回一个新的集合,因此 withFilter 在性能上会更加高一些。示例:

1
2
val t = Traversable(1 to 10: _*)
t.withFilter(_ % 2 == 0).withFilter(_ > 6).foreach(println) // 输出:2 和 8

九. 归约操作

9.1 scan & scanLeft

假设我们希望计算 [1, 5] 区间数据的阶乘,最简单的方式就是定义一个计算阶乘的函数,然后遍历应用集合中的每个元素,但是这样每次都需要从 1 开始执行计算,而不能复用之前的计算结果,实际上 5! = 5 * 4!,我们计算完 4 的阶乘之后乘以 5 即得到 5 的阶乘,而不需要重 1 开始重新计算。

使用 scan 函数我们可以做到复用,函数 scan 的定义如下,它接收一个初始值 z 和一个操作符 op, 前一次的计算结果会作为初始值传递给下一次计算

1
def scan[B >: A, That](z: B)(op: (B, B) => B)(implicit cbf: CanBuildFrom[Repr, B, That]): That

计算阶乘的示例:

1
2
val t = Traversable(1 to 5: _*)
t.scan(1)(_ * _) // 输出:List(1, 1, 2, 6, 24, 120)

函数 scan 只是 scanLeft 的别名,本质上就是 scanLeft。

9.2 scanRight

函数 scan 从左往右对集合进行遍历,而 scanRight 则从右往左对集合进行遍历,示例:

1
2
val t = Traversable(1 to 5: _*)
t.scanRight(1)(_ * _) // 输出:List(120, 120, 60, 20, 5, 1)

函数 scanRight 会从右往左对集合中的元素进行遍历,并将计算结果按照同样的顺序记录到结果集合中,同时将中间结果传递给下一次计算作为初始值。

9.3 fold & foldLeft

函数 fold 的作用与 scan 有些相似,会将上一次计算得到的中间结果传递给下一次计算,但是区别于 scan,函数 fold 并不会输出中间结果,而只是返回函数最后一次计算得到的最终结果。

示例:

1
2
val t = Traversable(1 to 10: _*)
val sum = t.fold(0)(_ + _) // 输出:55

上述示例使用 fold 对集合中的元素进行求和,本质上与 sum 函数是一致的,实际上 sum 底层也是依赖于 fold 实现的。

函数 fold 只是 foldLeft 的别名,本质上就是 foldLeft。

Scala 为 foldLeft 提供了简写版的 /: 函数,上面的示例可以改写如下:

1
val sum = (0 /: t) (_ + _)

9.4 foldRight

函数 foldRight 用于对集合中元素从右往左进行遍历,并应用计算,示例:

1
2
val t = Traversable("a", "b", "c")
t.foldRight("x")(_ + _) // 输出:abcx

Scala 也为 foldRight 提供了简写版的 :\ 函数,上面的示例可以改写如下:

1
val res = (t :\ "x") (_ + _)

9.5 reduce & reduceOption & reduceLeft & reduceLeftOption

函数 reduce 在功能上与 fold 相同,只是不需要提供初始值,函数 reduce 会以集合的第 1 个元素作为初始值,并提供从左往右的归约计算。示例:

1
2
3
4
5
val t = Traversable(1 to 10: _*)
t.reduce(_ + _) // 输出:55
t.reduceLeft(_ + _) // 输出:55
t.reduceOption(_ + _) // 输出:Some(55)
t.reduceLeftOption(_ + _) // 输出:Some(55)

其实函数 reduce 和 reduceOption 分别对应函数 reduceLeft 和 reduceLeftOption 的别名,二者的区别在于当集合为空时,reduce 会抛出异常,而 reduceOption 只是返回 None。如果集合中只有 1 个元素,那么两个函数均返回该元素,而不是抛出异常。

9.6 reduceRight & reduceRightOption

函数 reduceRight 对标 reduceLeft,函数 reduceRightOption 对标 reduceLeftOption,区别仅在于是从右往左进行计算,示例:

1
2
3
val t = Traversable(1 to 10: _*)
t.reduceRight(_ * 10 + _) // 输出:460
t.reduceRightOption(_ * 10 + _) // 输出:Some(460)

十. 元素获取与检索

10.1 head & headOption

函数 head 和 headOption 都是用于从 Traversable 对象中获取第一个元素,区别在于前者在元素不存在时抛出 NoSuchElementException 异常,而后者返回 None。示例:

1
2
3
val t = Traversable(1 to 10: _*)
t.head // 输出:1
t.headOption // 输出:Some(1)

10.2 last & lastOption

函数 last 和 lastOption 都是用于从 Traversable 对象中获取最后一个元素,区别在于前者在元素不存在时抛出 NoSuchElementException 异常,而后者返回 None。示例:

1
2
3
val t = Traversable(1 to 10: _*)
t.last // 输出:10
t.lastOption // 输出:Some(10)

注意 :对于 Traversable 来说,默认的 last 实现会遍历整个集合,时间复杂度为 O(n)

10.3 find

函数 find 用于从 Traversable 对象中基于给定的筛选条件 A => Boolean 选择第一个满足条件的元素,如果不存在则返回 None。示例:

1
2
val t = Traversable(1 to 10: _*)
t.find(_ % 2 == 0) // 输出:Some(2)

10.4 tail & tails

前面介绍了 head 函数用于返回 Traversable 对象的第一个元素,而 tail 函数正好与 head 函数互补,用于返回 Traversable 对象除第一个元素以外的剩余元素。示例:

1
2
val t = Traversable(1 to 10: _*)
t.tail // 输出:List(2, 3, 4, 5, 6, 7, 8, 9, 10)

我们可以说一个集合是由 head 和 tail 组成的:head :: tail

函数 tails 与 tail 的作用类似,但是多了一个 s,所以该函数的返回结果是一个集合,可以将 tails 看做是对集合迭代执行 tail 操作并生成结果集,其中第一个结果是原集合,而最后一个结果是空集合。示例:

1
2
val t = Traversable(1 to 10: _*)
t.tails.foreach(println)

输出:

1
2
3
4
5
6
7
8
9
10
11
List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
List(2, 3, 4, 5, 6, 7, 8, 9, 10)
List(3, 4, 5, 6, 7, 8, 9, 10)
List(4, 5, 6, 7, 8, 9, 10)
List(5, 6, 7, 8, 9, 10)
List(6, 7, 8, 9, 10)
List(7, 8, 9, 10)
List(8, 9, 10)
List(9, 10)
List(10)
List()

10.5 init & inits

函数 init 的作用正好与 tail 相反,它与 last 函数正好互补,用于返回 Traversable 对象除最后一个元素以外的剩余元素。示例:

1
2
val t = Traversable(1 to 10: _*)
t.init // 输出:List(1, 2, 3, 4, 5, 6, 7, 8, 9)

而 inits 函数的作用也正好与 tails 相反,示例:

1
2
val t = Traversable(1 to 10: _*)
t.inits.foreach(println)

输出:

1
2
3
4
5
6
7
8
9
10
11
List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
List(1, 2, 3, 4, 5, 6, 7, 8, 9)
List(1, 2, 3, 4, 5, 6, 7, 8)
List(1, 2, 3, 4, 5, 6, 7)
List(1, 2, 3, 4, 5, 6)
List(1, 2, 3, 4, 5)
List(1, 2, 3, 4)
List(1, 2, 3)
List(1, 2)
List(1)
List()

10.6 take & takeWhile

函数 take 用于从 Traversable 中获取前 n 个元素(如果集合长度小于 n,则返回全部元素),示例:

1
2
val t = Traversable(1, 2, 3, 4, 5, 4, 3, 2, 1)
t.take(5) // 输出:List(1, 2, 3, 4, 5)

函数 takeWhile 接收一个谓词 A => Boolean,用于从左往右对 Traversable 对象中的元素进行筛选,直到第一个不满足条件的元素为止。示例:

1
2
val t = Traversable(1, 2, 3, 4, 5, 4, 3, 2, 1)
t.takeWhile(_ <= 3) // 输出:List(1, 2, 3)

10.7 drop & dropWhile

函数 drop 与 take 刚好相反,用于获取 Traversable 对象中除前 n 个元素之外的元素(如果集合长度小于 n,则返回空集合),示例:

1
2
val t = Traversable(1, 2, 3, 4, 5, 4, 3, 2, 1)
t.drop(5) // 输出:List(4, 3, 2, 1)

如果 n 小于等于 0,则返回整个集合。

函数 dropWhile 与 takeWhile 刚好相反,它也接收一个谓词 A => Boolean,用于从左往右对 Traversable 对象中的元素进行筛选并跳过开头连续满足谓词的的元素,并返回该元素之后元素组成的集合。示例:

1
2
val t = Traversable(1, 2, 3, 4, 5, 4, 3, 2, 1)
t.dropWhile(_ <= 3) // 输出:List(4, 5, 4, 3, 2, 1)

10.8 takeRight & dropRight

函数 takeRight 用于获取 Iterable 集合的后 n 个元素,而函数 dropRight 用于阶段 Iterable 集合的后 n 个元素,示例:

1
2
3
val itr = Iterable(1 to 9: _*)
itr.takeRight(3) // 输出:List(7, 8, 9)
itr.dropRight(3) // 输出:List(1, 2, 3, 4, 5, 6)

10.9 slice

函数 slice 用于获取原 Traversable 对象的一个子集合,函数的定义为 slice(from: Int, until: Int),第 2 个参数命名为 until,所以我们可以知道截取的是一个 左闭右开 的区间。示例:

1
2
val t = Traversable(1 to 10: _*)
t.slice(3, 5) // 输出:List(4, 5)

注意 :如果 from 或 until 的参数值超过了集合的上下标,则以集合的上下标为准,不会抛出异常。

十一. 分组操作

11.1 splitAt

函数 splitAt 接收一个参数 n,并以位置 n 将 Traversable 集合分割成前后两部分,示例:

1
2
val t = Traversable(1 to 10: _*)
t.splitAt(3) // 输出:(List(1, 2, 3),List(4, 5, 6, 7, 8, 9, 10))

功能上类似于 (t.take(n), t.drop(n))

11.2 span

函数 span 接收一个谓词 A => Boolean,并且从左往右对 Traversable 集合进行遍历,将开头连续满足条件的元素分为一组,剩下的元素分为一组。示例:

1
2
val t = Traversable(1 to 10: _*)
t.span(_ < 4) // 输出:(List(1, 2, 3),List(4, 5, 6, 7, 8, 9, 10))

功能上类似于 (t.takeWhile(n), t.dropWhile(n))

11.3 partition

函数 partition 接收一个谓词 A => Boolean,相对于 span 的区别在于它会对集合中所有的元素进行筛选,并将满足条件的元素分为一组,不满足条件的元素分为另一组。示例:

1
2
val t = Traversable(1 to 10: _*)
t.partition(_ % 2 == 0) // 输出:(List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))

11.4 groupBy

函数 groupBy 接收一个 A => K 类型参数,对 Traversable 对象中的元素进行计算并得到一个 K 类型的值,然后以 K 值作为 key,对应的集合元素作为 value,构建 Map 结果集。示例:

1
2
val t = Traversable(1 to 10: _*)
t.groupBy(_ % 3).foreach(println)

输出:

1
2
3
(2,List(2, 5, 8))
(1,List(1, 4, 7, 10))
(0,List(3, 6, 9))

11.5 grouped

函数 grouped 用于对 Iterable 对象中的元素进行分组,该函数接收一个 size 参数,用于将原集合分组成长度为指定大小的多个子集合,对于最后一个子集合,其长度可能小于 size。示例:

1
2
3
val itr = Iterable(1 to 9: _*)
val grouped = itr.grouped(4)
grouped.foreach(println)

输出:

1
2
3
List(1, 2, 3, 4)
List(5, 6, 7, 8)
List(9)

11.6 sliding

函数 sliding 用于对 Iterable 对象进行窗口操作,该函数定义如下,其中参数 size 用于指定窗口的大小,而参数 step 用于指定每次滑动的步长(默认为 1):

1
2
def sliding(size: Int): Iterator[Repr]
def sliding(size: Int, step: Int): Iterator[Repr]

示例:

1
2
val itr = Iterable(1 to 9: _*)
itr.sliding(3, 2).foreach(println)

输出:

1
2
3
4
List(1, 2, 3)
List(3, 4, 5)
List(5, 6, 7)
List(7, 8, 9)

十二. 检查操作

12.1 forall & exist

函数 forall 和 exist 都接收一个谓词 A => Boolean,用于对集合中的元素进行检查,区别在于前者会对所有的元素进行校验,并在所有元素都满足条件时返回 true,而后者只需要有一个元素满足条件即返回 true。示例:

1
2
3
val t = Traversable(1 to 10: _*)
t.forall(_ > 0) // 输出:true
t.exists(_ % 2 == 0) // 输出:true

注意 :对于一个空集合,函数 forall 会返回 true。

12.2 count

函数 count 接收一个谓词 A => Boolean,该函数会对 Traversable 对象中所有的元素进行检查,并返回满足条件的元素个数,示例:

1
2
val t = Traversable(1 to 10: _*)
t.count(_ % 2 == 0) // 输出:5

注意 :不推荐使用 t.filter(_ % 2 == 0).size 进行计算,因为这样会生成一个中间集合,性能较差。

十三. 聚合操作

聚合操作对集合中的元素执行计算,并返回单一的值。

13.1 sum & product

函数 sum 和 product 分别用于求解集合中元素的 ,示例:

1
2
3
val t = Traversable(1 to 10: _*)
t.sum // 输出:55
t.product // 输出:3628800

13.2 min & max

函数 min 和 max 分别用于求解集合中元素的最小值和最大值,示例:

1
2
3
val t = Traversable(1 to 10: _*)
t.min // 输出:1
t.max // 输出:10

其中 min 和 max 函数的定义如下:

1
2
min[B >: A](implicit cmp: Ordering[B]): A
max[B >: A](implicit cmp: Ordering[B]): A

它们都接受一个隐式参数 cmp,我们可以利用该参数自定义比较器。

13.3 minBy & maxBy

函数 min 和 max 都是依据集合中元素值本身进行比较,而函数 minBy 和 maxBy 则允许我们指定比较的因子,示例:

1
2
3
val t = Traversable("111", "2", "33")
t.minBy(_.length) // 输出:2
t.maxBy(_.toInt) // 输出:111

13.4 aggregate

函数 aggregate 是一个比 fold 和 reduce 更加抽象的高阶函数,应用上更加灵活,该函数不要求输出的结果必须是集合元素类型的父类型。函数 aggregate 定义如下:

1
aggregate[B](z: =>B)(seqop: (B, A) => B, combop: (B, B) => B): B

其中 z 是初始值,seqop 用于在遍历分区的时候更新结果,combop 用于汇总各个分区的结果。

示例:

1
2
val t = Traversable("111", "2", "33")
t.aggregate(0)(_ + _.toInt, _ + _) // 输出:146

上述示例将集合中的每个元素都转换成 Int 类型,并使用 seqop 执行求和操作,其中初始值 z 设置为 0。这里因为没有启用并行计算,所以只有一个分组在运行,对应的 combop 没有意义,我们可以将所有分区的求和结果置为 0,即 t.aggregate(0)(_ + _.toInt, (_, _) => 0),对应的结果不会变化。但是如果我们启用并行计算,即 t.par.aggregate(0)(_ + _.toInt, (_, _) => 0),那么结果就会是 0,改为 t.par.aggregate(0)(_ + _.toInt, _ + _) 即能拿到正确结果。

十四. 生成字符串

14.1 mkString & addString

函数 mkString 用于对 Traversable 对象中的元素拼接生成字符串,并且允许指定元素的分隔符,以及前缀和后缀。示例:

1
2
3
val t = Traversable(1 until 10: _*)
t.mkString(", ") // 输出:1, 2, 3, 4, 5, 6, 7, 8, 9
t.mkString("[", ", ", "]") // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

函数 addString 同样用于对 Traversable 对象中的元素拼接生成字符串, 相对于 mkString 的区别在于需要提供 StringBuilder 对象 ,同样允许指定分隔符、前缀和后缀。 事实上 mkString 就是利用 addString 实现的 。示例:

1
2
3
4
val t = Traversable(1 until 10: _*)
t.addString(new StringBuilder()) // 输出:123456789
t.addString(new StringBuilder(), ", ") // 输出:1, 2, 3, 4, 5, 6, 7, 8, 9
t.addString(new StringBuilder(), "[", ", ", "]") // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

14.2 stringPrefix

函数 stringPrefix 用于返回集合对象的实际类型名称,示例:

1
2
val t = Traversable(1 until 10: _*)
t.stringPrefix // 输出:List

十五. 复制元素到数组

15.1 copyToArray & copyToBuffer

函数 toArray 可以将一个 Traversable 对象转换成一个数组对象,如果我们希望将 Traversable 对象中已有的元素复制到一个已有的数组中,那么可以使用 copyToArray 函数。示例:

1
2
3
4
val t = Traversable(1 until 10: _*)
val res = new Array[Int](t.size / 2)
t.copyToArray(res, 0, res.length)
res.mkString(", ") // 输出:1, 2, 3, 4

函数 copyToArray 包含 3 个重载版本,如下:

1
2
3
copyToArray[B >: A](xs: Array[B]): Unit
copyToArray[B >: A](xs: Array[B], start: Int): Unit
copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Unit

其中参数 start 对应目标数组的下标,表示待复制的元素将写入数组的哪个位置,默认为 0,参数 len 表示要复制的元素长度,默认为集合的长度,如果 len 超过了集合的长度,则以集合长度为准。

函数 copyToBuffer 用于将元素复制到所提供的 buffer 对象中,示例:

1
2
3
4
val t = Traversable(1 until 10: _*)
val buffer = mutable.Buffer[Int]()
t.copyToBuffer(buffer)
buffer // 输出:ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9)

函数 copyToBuffer 没有提供其它重载版本。

十六. 生成视图

16.1 view

函数 view 用于生成 Traversable 对象的视图(相当于对原集合对象的引用),它接收两个参数 from 和 until,用于指定目标视图的生成区间,如果不指定这 2 个参数则创建整个 Traversable 对象中元素的视图。示例:

1
2
3
4
5
6
7
8
9
10
11
val t = mutable.Seq(1 until 10: _*)
val v = t.view(0, 3)
val s = t.slice(0, 3)
val f = v.force // 严格模式
t(0) = 10
v.mkString(", ") // 输出:10, 2, 3
f.mkString(", ") // 输出:1, 2, 3
s.mkString(", ") // 输出:1, 2, 3
t(0) = 8
v.mkString(", ") // 输出:8, 2, 3
f.mkString(", ") // 输出:1, 2, 3

函数 view 与 slice 的区别

函数 view 生成集合的一个非严格模式(non-strict)视图,即 view 是延迟计算的,如果希望转换成严格模式,则可以调用视图的 force 函数,非严格模式的视图可以看做是对原集合区间的一个引用,当原集合区间中的元素发生变更时,会反应到视图上。

参考