Scala 容器操作介绍

摘要:本文基于 Programming Scala 2nd 介绍一下容器的相关操作

遍历 foreach

这里给出两个例子,分别是遍历列表和 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 遍历列表
List(1, 2, 3, 4, 5) foreach { i => println("Int: " + i) }

// 遍历 Map 的两种方式
val stateCapitals = Map(
"Alabama" -> "Montgomery",
"Alaska" -> "Juneau",
"Wyoming" -> "Cheyenne")

// 第一种很好理解,kv 是 map 的元素,是一个二元元组所以可以分别获取两个元素。
stateCapitals foreach { kv => println(kv._1 + ": " + kv._2) }

// 匿名函数中运用 case 语句,实际上定义了一个偏函数,但这个函数实际上并不“偏”,因为它可以匹配所有输入。
stateCapitals foreach { case (k, v) => println(k + ": " + v) }

// 我们可以尝试case 一个元素,会发现直接将元组打印。
stateCapitals foreach { case (k) => println(k) }
stateCapitals foreach { k => println(k) }

// 交换操作
stateCapitals foreach { x => println(x.swap._1 + ": " + x.swap._2)}

映射 map

我们之前已经接触过 map 方法, map 方法返回一个与原集合类型大小相同的新集合,其中的每个元素均由原集合的对应元素转换得到。

下面给出具体使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
val stateCapitals = Map(
"Alabama" -> "Montgomery",
"Alaska" -> "Juneau",
"Wyoming" -> "Cheyenne")

// 从一个 string 到 string 的 Map 变成一个 string 到 integer 的 Map
val lengths = stateCapitals map {
kv => (kv._1, kv._2.length)
}
println(lengths)
// lengths: scala.collection.immutable.Map[String,Int] =
// Map(Alabama -> 10, Alaska -> 6, Wyoming -> 8)


val caps = stateCapitals map {
case (k, v) => (k, v.toUpperCase)
}
println(caps)
// caps: scala.collection.immutable.Map[String,String] =
// Map(Alabama -> MONTGOMERY, Alaska -> JUNEAU, Wyoming -> CHEYENNE)


val stateCapitals2 = stateCapitals + ("Virginia" -> "Richmond")
println(stateCapitals2)
// stateCapitals2: scala.collection.immutable.Map[String,String] =
// Map(Alabama -> Montgomery, Alaska -> Juneau, Wyoming -> Cheyenne, Virginia -> Richmond)

val stateCapitals3 = stateCapitals2 + (
"New York" -> "Albany", "Illinois" -> "Springfield")
println(stateCapitals3)

// stateCapitals3: scala.collection.immutable.Map[String,String] =
// Map(Alaska -> Juneau, Virginia -> Richmond, Alabama -> Montgomery, New York -> Albany, Illinois -> Springfield, Wyoming -> Cheyenne)

扁平映射 flatmap

flatMap 是 Map 操作的一种推广。在 flatMap 中,我们对原始集合中的每个元素,都分别产生零或多个元素。我们传入一个函数,该函数对每个输入返回一个集合,而不是一个元素。然后 flatMap 把生成的多个集合“压扁”为一个集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
val list = List("now", "is", "", "the", "time")

// toList 表示将传入的值变成 list,也就是说把 now 变成 list n o w。
list flatMap (s => s.toList)
// res0: List[Char] = List(n, o, w, i, s, t, h, e, t, i, m, e)

// 我们再举几个例子

list flatMap (s => s.getBytes)
// res1: List[Byte] = List(110, 111, 119, 105, 115, 116, 104, 101, 116, 105, 109, 101)
list flatMap (s => s.split("h")) // 这个动作会将压扁操作更改为分割,也就是不会打散字符串,而是分割字符串。
// res2: List[String] = List(now, is, , t, e, time)
list flatMap (s => s.toUpperCase)
// res3: List[Char] = List(N, O, W, I, S, T, H, E, T, I, M, E)
list flatMap (s => s) // 可以看到默认的操作就是将 string 进行压扁
list.flatten // 这两个等价
// res4: List[Char] = List(n, o, w, i, s, t, h, e, t, i, m, e)

val listInt = List(1,2,3,4,5,6,7)
// listInt faltMap (x => x)
// Error:(12, 76) value faltMap is not a member of List[Int]

val listMultiLevel = List(List("inner", "is", "block"), "is", List("inner2", "is2", "block2"), "the", "time")

listMultiLevel flatMap {
case list: List[String] => list;
case str: String => str
}
// res5: List[Any] = List(inner, is, block, i, s, inner2, is2, block2, t, h, e, t, i, m, e)
// 如果有多层,只能压扁一层,不过可以自己压扁内层

listMultiLevel flatMap {
case list: List[String] => list flatMap( x => x);
case str: String => str
}

// 或者

listMultiLevel flatMap {
case list: List[String] => list.flatten
case str: String => str
}
// res5: List[Char] = List(i, n, n, e, r, i, s, b, l, o, c, k, i, s, i, n, n, e, r, 2, i, s, 2, b, l, o, c, k, 2, t, h, e, t, i, m, e)


// flatMap 的行为很像先调用 map,再调用另一个方法 flatten:

// Like flatMap, but flatMap eliminates the intermediate step!
val list2 = List("now", "is", "", "the", "time") map (s => s.toList)
list2.flatten

// list2: List[List[Char]] = List(List(n, o, w), List(i, s), List(), List(t, h, e), List(t, i, m, e))
// res1: List[Char] = List(n, o, w, i, s, t, h, e, t, i, m, e)

过滤 fitler

遍历一个集合,然后抽取其中满足特定条件的元素,组成一个新的集合

1
2
3
4
5
6
7
8
9
val stateCapitals = Map(
"Alabama" -> "Montgomery",
"Alaska" -> "Juneau",
"Wyoming" -> "Cheyenne")

val map2 = stateCapitals filter { kv => kv._1 startsWith "A" }
// map2: scala.collection.immutable.Map[String,String] = Map(Alabama -> Montgomery, Alaska -> Juneau)
// 过滤map的 key,只保留以 A 开头的条目
println( map2 )

用于完成集合的过滤作用或者返回原始集合中的一部分元素。一些方法在输入无限集合时不会返回;一些方法在输入同一个集合时,除非集合的遍历顺序固定,否则多次运行的情况下会产生不同的输出。也就是说并不是只有 filter 能实现过滤的目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
• def drop (n : Int) : TraversableLike.Repr
除起始的 n 个元素,选择其他所有的元素组成一个新的集合并返回。如果原始集合包含
的元素个数小于 n,则方法会返回一个空集合。
• def dropWhile (p : (A) => Boolean) : TraversableLike.Repr
从头遍历,丢弃满足一定谓词的最长集合前缀。返回一个最长集合后缀,其第一个元素
不满足指定的谓词 p。
• def exists (p : (A) => Boolean) : Boolean
测试在集合中是否至少有一个元素满足给定的谓词,如果存在则返回 true,否则返回
false。
• def filter (p : (A) => Boolean) : TraversableLike.Repr
选择集合中所有满足一定谓词的元素,返回的新集合中包含了所有满足该谓词 p 的元
素。元素在原集合中的顺序可以得到保持。
• def filterNot (p : (A) => Boolean) : TraversableLike.Repr
是 filter 的“反义词”。在遍历原集合时,选择那些不满足给定谓词 p 的元素并组成新
集合返回。
• def find (p : (A) => Boolean) : Option[A]
遍历原集合,寻找第一个满足给定谓词的元素。如果存在这一元素,返回 Option,且
Option 中包含满足谓词 p 的第一个元素;否则返回 None。
• def forall (p : (A) => Boolean) : Boolean
测试集合中所有元素是否均满足给定的谓词。如果所有元素均满足谓词 p,则返回 true,
否则返回 false。
• def partition (p : (A) => Boolean): (TraversableLike.Repr, TraversableLike.Repr)
根据谓词,将可遍历集合分成两个子集合。返回值是两个集合:第一个集合包含所有满
足谓词 p 的元素,而第二个集合包含所有不满足谓词 p 的元素。两个集合中元素间的顺
序均与原集合保持一致。
• def take (n : Int) : TraversableLike.Repr
选择前 n 个元素。返回一个可遍历集合,包含原集合的前 n 个元素,如果原集合包含的
元素小于 n 个,则返回原集合本身。
• def takeWhile (p : (A) => Boolean) : TraversableLike.Repr
选择满足特定谓词的最长集合前缀。返回的可遍历集合包含一个最长集合前缀,其中的
每个元素均满足谓词 p。

折叠与归约

我们把折叠和归约放在一起讨论是因为两者很相似。它们都是将一个集合“缩小”成一个更小的集合或一个值的操作。

折叠从一个初始的“种子”值开始,然后以该值作为上下文,处理集合中的每个元素。不同的是,归约不需要调用者提供一个初始值。它将集合的其中一个元素当做初始值,通常这个值是集合的第一个元素或最后一个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
List(1,2,3,4,5,6) reduce (_ + _)
//res0: Int = 21

List(1,2,3,4,5,6) reduce (_ * _)
// result Int = 720
List(1,2,3,4,5,6).fold (10) (_ * _)
(List(1,2,3,4,5,6) fold 10) (_ * _) // 中缀表示法
//res2: Int = 7200 增加一个初始值 10,在归约的基础上乘以 10.


// 是一个偏方法,然后可以用这个偏方法继续操作
val fold1 = (List(1,2,3,4,5,6) fold 10) _
fold1(_ * _)
//fold1: ((Int, Int) => Int) => Int = <function1>
//res3: Int = 7200

// 如果我们对任何一个空的集合执行 fold 操作,它会返回初始种子的值。与此不同, reduce
// 无法对空集合进行操作,因为 reduce 没有值可以返回。
(List.empty[Int] fold 10) (_ + _)
try {
List.empty[Int] reduce (_ + _)
} catch {
case e: java.lang.UnsupportedOperationException => e
}
//res4: Int = 10
//res5: Any = java.lang.UnsupportedOperationException: empty.reduceLeft


//如果不确认集合是否为空,这时候需要使用 reduceOption
List.empty[Int] reduceOption (_ + _)
List(1,2,3,4,5,6) reduceOption (_ * _)
//res6: Option[Int] = None
//res7: Option[Int] = Some(720)