Scala 集合:基础 API

栏目: Scala · 发布时间: 6年前

内容简介:Traversable 和 Iterable 特质定义了 scala 集合的基本操作,后续文章中将要介绍的 Seq、Set,以及 Map 等集合都实现了这两个特质。本文主要对 Traversable 和 Iterable 中定义的方法进行归类和介绍,了解这些方法也就基本知道了 scala 集合的大部分操作。Traversable 定义为 Trait 类型,包含 2 个直接派生的子特质Traversable 特质的定义如下:

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

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

Traversable 特质的定义如下:

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 函数得到这个具体的实现类对象:

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

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

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 可以生成指定维度的集合,并使用给定的值对集合进行填充。示例:

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 内容如下:

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 类型,允许我们使用不同的元素对集合进行填充,例如示例中指定的随机数函数。

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 的定义如下:

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]]]]]

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

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

输出:

1.3 iterate

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

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

示例:

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 用于指定步进值:

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

示例:

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 函数实现。示例:

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] 类型的集合。示例:

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 类型,就可以实现转换。示例:

// 隐式转换
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 对象中包含的元素是多层嵌套集合,能否平展?

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

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

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

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

三. 转置操作

3.1 transpose

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

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

实现:

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 对象执行拉拉链操作,并 以较短的集合为准,忽略较长集合中多出来的元素 ,示例:

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 以较长的集合为准,并用提供的默认值对较短的集合进行弥补 。示例:

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 的定义如下:

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 对象中的元素与集合下标进行拉拉链操作,示例:

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

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

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

示例:

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 的定义如下:

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

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

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 的定义如下:

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

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

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 个对象中的元素进行连接,可以使用 ++ 函数,示例:

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

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

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 的实现中,结果类型与左边的集合类型保持一致,示例:

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 的要求。对应字符编码之后的值如下:

~ -> $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 函数,它会预先计算出所需的结果集合大小,然后生成结果,减少了中间临时集合对象的生成。示例:

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 类型的参数:

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

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

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。示例:

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

调用:

"-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 对象中的元素进行筛选,区别在于前者保留满足筛选条件的元素,而后者保留不满足筛选条件的元素。示例:

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 特质,定义如下:

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 在性能上会更加高一些。示例:

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, 前一次的计算结果会作为初始值传递给下一次计算

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

计算阶乘的示例:

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

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

9.2 scanRight

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

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 并不会输出中间结果,而只是返回函数最后一次计算得到的最终结果。

示例:

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

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

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

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

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

9.4 foldRight

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

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

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

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

9.5 reduce & reduceOption & reduceLeft & reduceLeftOption

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

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,区别仅在于是从右往左进行计算,示例:

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

十. 元素获取与检索

10.1 head & headOption

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

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

10.2 last & lastOption

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

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。示例:

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

10.4 tail & tails

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

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 操作并生成结果集,其中第一个结果是原集合,而最后一个结果是空集合。示例:

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

输出:

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 对象除最后一个元素以外的剩余元素。示例:

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

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

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

输出:

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,则返回全部元素),示例:

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 对象中的元素进行筛选,直到第一个不满足条件的元素为止。示例:

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,则返回空集合),示例:

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 对象中的元素进行筛选并跳过开头连续满足谓词的的元素,并返回该元素之后元素组成的集合。示例:

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 个元素,示例:

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,所以我们可以知道截取的是一个 左闭右开 的区间。示例:

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

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

十一. 分组操作

11.1 splitAt

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

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 集合进行遍历,将开头连续满足条件的元素分为一组,剩下的元素分为一组。示例:

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 的区别在于它会对集合中所有的元素进行筛选,并将满足条件的元素分为一组,不满足条件的元素分为另一组。示例:

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 结果集。示例:

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

输出:

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

11.5 grouped

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

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

输出:

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

11.6 sliding

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

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

示例:

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

输出:

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。示例:

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

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

12.2 count

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

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

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

十三. 聚合操作

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

13.1 sum & product

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

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

13.2 min & max

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

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

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

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 则允许我们指定比较的因子,示例:

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

13.4 aggregate

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

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

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

示例:

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 对象中的元素拼接生成字符串,并且允许指定元素的分隔符,以及前缀和后缀。示例:

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 实现的 。示例:

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 用于返回集合对象的实际类型名称,示例:

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

十五. 复制元素到数组

15.1 copyToArray & copyToBuffer

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

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 个重载版本,如下:

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 对象中,示例:

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 对象中元素的视图。示例:

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 函数,非严格模式的视图可以看做是对原集合区间的一个引用,当原集合区间中的元素发生变更时,会反应到视图上。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

On LISP

On LISP

Paul Graham / Prentice Hall / 09 September, 1993 / $52.00

On Lisp is a comprehensive study of advanced Lisp techniques, with bottom-up programming as the unifying theme. It gives the first complete description of macros and macro applications. The book also ......一起来看看 《On LISP》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码