Scala 集合:Map API

Map 定义了键值对的特质类型,区分可变与不可变,间接继承了偏函数 PartialFunction 特质,所以一个 Map 本质上也是一个偏函数,其定义如下:

1
trait Map[K, +V] extends Iterable[(K, V)] with GenMap[K, V] with MapLike[K, V, Map[K, V]]

其伴生对象提供了构造 Map 对象的简单方式,示例:

1
2
val map1 = Map("name" -> "zhenchao", "age" -> 28)
val map2 = Map(("name", "zhenchao"), ("age", 28))

可变 Map 与不可变 Map 的相互转换:

  • 可变 -> 不可变

将可变 Map 转换成不可变 Map 的最简单方式就是调用 toMap 函数,但是如果希望转换成特定类型的不可变 Map 类型,则需要借助 ++ 函数,示例:

1
2
3
4
5
val mmap = mutable.Map("name" -> "zhenchao", "age" -> 28)
val map = mmap.toMap
map // 输出:Map(age -> 28, name -> zhenchao)
val tmap = mutable.TreeMap.empty[String, AnyVal] ++ mmap
tmap // 输出:TreeMap(age -> 28, name -> zhenchao)
  • 不可变 -> 可变

不可变 Map 转换成可变 Map 也需要借助 ++ 函数实现,示例:

1
2
3
val map = Map("name" -> "zhenchao", "age" -> 28)
val mmap = mutable.Map.empty[String, AnyVal] ++ map
mmap // 输出:Map(name -> zhenchao, age -> 28)

一. 查询操作

1.1 get & getOrElse & apply

函数 get、getOrElse 和 apply 均用于从 Map 对象中获取元素,区别在于 get 操作返回 Option 类型,getOrElse 允许在对应 key 不命中时返回默认的 value,而 apply 同样是返回指定的 key 对应的 value,但是在 key 不命中时默认抛出 NoSuchElementException 异常。示例:

1
2
3
4
5
val map = Map("name" -> "zhenchao", "age" -> 28)
map.get("name") // 输出:zhenchao
map.getOrElse("school", "WHU") // 输出:WHU
map.apply("age") // 输出:28
map("age") // 输出:28

函数 apply 同样可以使用 () 表达式简写。

1.2 withDefault & withDefaultValue

上面在使用 apply 函数时,在 key 不命中的情况下默认会抛出 NoSuchElementException,如果希望改变这种行为,我们可以使用 withDefault 或 withDefaultValue 函数自定义默认行为,其中 withDefault 接收一个函数(入参是 key),而 withDefaultValue 则接收一个默认值。示例:

1
2
3
4
5
val map = Map("name" -> "zhenchao", "age" -> 28)
val map1 = map.withDefault(key => "invalid key: " + key)
val map2 = map.withDefaultValue("unknown")
map1("school") // 输出:invalid key: school
map2("school") // 输出:unknown

1.3 getOrElseUpdate

对于可变集合来说,可以使用 getOrElseUpdate 函数(定义如下),该函数允许提供一个 op 操作,如果对应的 key 存在则返回对应的 value,否则会计算 op 得到一个 value,并更新 Map 对象,同时返回该 value。

1
def getOrElseUpdate(key: K, op: => V): V

示例:

1
2
val mmap = mutable.Map("name" -> "zhenchao", "age" -> 28)
mmap.getOrElseUpdate("school", "WHU") // 输出:WHU

二. 插入操作

插入操作允许 value 为 null,但不允许 key 为 null,此外对于已经存在的 key,插入操作相当于更新操作。

2.1 +

函数 + 用于往 Map 对象中添加一个或多个键值对,函数定义如下:

1
2
def + [V1 >: V](kv: (K, V1)): Map[K, V1]
def + [V1 >: V] (elem1: (K, V1), elem2: (K, V1), elems: (K, V1) *): immutable.Map[K, V1]

示例:

1
2
3
val map = Map("name" -> "zhenchao", "age" -> 28)
map + ("school" -> "WHU") // 输出:Map(name -> zhenchao, age -> 28, school -> WHU)
map + ("school" -> "CMU", "gender" -> "M") // 输出:Map(name -> zhenchao, age -> 28, school -> CMU, gender -> M)

2.2 ++ & ++:

函数 ++ 接收一个 GenTraversableOnce 类型的参数,只要是继承 GenTraversableOnce 的集合都可以作为参数,函数会将参数中所包含的元素添加到原 Map 对象中,并创建一个新的 Map 返回。示例:

1
2
3
val map = Map("name" -> "zhenchao", "age" -> 28)
map ++ Seq("school" -> "CMU", "gender" -> "M") // 输出:Map(name -> zhenchao, age -> 28, school -> CMU, gender -> M)
map ++: Seq("school" -> "CMU", "gender" -> "M") // 输出:List((name,zhenchao), (age,28), (school,CMU), (gender,M))

函数 ++:++ 的右结合版本。

2.3 += & ++= & put

对于可变集合而言,可以实现原地插入,函数 += 对标 +,函数 ++= 对标 ++,函数 put 用于插入单个键值对,并返回一个 Option 类型,表示插入操作之前对应的 value。示例:

1
2
3
4
5
6
7
8
val mmap = mutable.Map("name" -> "zhenchao", "age" -> 28)
mmap += ("school" -> "WHU")
mmap += ("school" -> "CMU", "gender" -> "M")
mmap // 输出:Map(school -> CMU, age -> 28, name -> zhenchao, gender -> M)
mmap ++= Seq("age" -> 29, "gender" -> "M")
mmap // 输出:Map(school -> CMU, age -> 29, name -> zhenchao, gender -> M)
mmap.put("country", "China")
mmap // 输出:Map(school -> CMU, country -> China, age -> 29, name -> zhenchao, gender -> M)

三. 更新操作

3.1 updated

函数 updated 用于更新 Map 对象中指定的 key 和 value,如果不存在则添加,示例:

1
2
3
val map = Map("name" -> "zhenchao", "age" -> 28)
map.updated("age", 29) // 输出:Map(name -> zhenchao, age -> 29)
map.updated("school", "WHU") // 输出:Map(name -> zhenchao, age -> 28, school -> WHU)

3.2 update & put

对于可变集合而言,可以实现原地更新操作,函数 update 用于更新一个可变 Map 中的 key 和 value,如果不存在则会添加,效果上等价于 put,示例:

1
2
3
4
5
val mmap = mutable.Map("name" -> "zhenchao", "age" -> 28)
mmap.update("age", 29)
mmap.update("school", "WHU")
mmap("school") = "CMU"
mmap // 输出:Map(school -> CMU, age -> 29, name -> zhenchao)

函数 update 同样可以简写为 () 表达式。

四. 删除操作

4.1 - & --

函数 - 用于删除指定的一个或多个 key,而函数 -- 则接收一个 GenTraversableOnce 类型参数,用于批量删除给定集合中包含的所有 key,示例:

1
2
3
val map = Map("name" -> "zhenchao", "age" -> 28)
map - "name" // 输出:Map(age -> 28)
map -- Set("age", "school") // 输出:Map(name -> zhenchao)

4.2 -= & --= & remove & clear

对于可变集合而言,可以实现原地删除,函数 -= 对标 -,函数 --= 对标 --,示例:

1
2
3
4
val mmap = mutable.Map("name" -> "zhenchao", "age" -> 28)
mmap -= "name"
mmap --= Set("age", "school")
mmap // 输出:Map()

函数 remove 用于从可变 Map 对象中删除给定的 key,并返回 key 对应的 value,Option 类型。函数 clear 用于清空整个 Map 对象。

五. 包含检查

5.1 contains & isDefinedAt

函数 contains 和 isDefinedAt 均用于检查 Map 对象中是否包含指定的 key,示例:

1
2
3
val map = Map("name" -> "zhenchao", "age" -> 28)
map.contains("name") // 输出:true
map.isDefinedAt("school") // 输出:false

其中 isDefinedAt 继承自偏函数。

六. 获取键或值的集合

6.1 keys & keySet & keysIterator

函数 keys、keySet 和 keysIterator 均用于获取 Map 对象键的集合,区别在于函数 keys 返回的是 Iterable[K] 类型对象;函数 keySet 返回的是 Set[K] 类型对象;而函数 keysIterator 返回一个 Iterator[K] 类型对象。

6.2 values & valuesIterator

函数 values 和 valuesIterator 均用于获取 Map 对象值的集合,区别在于函数 values 返回的是 Iterable[V] 类型,而函数 valuesIterator 返回的是 Iterator[V] 类型。

七. 转换操作

Map 对象考虑其特性,增加了 filterKeys、mapValues 和 transform 函数。

7.1 filterKeys

函数 filterKeys 对 Map 对象中的 key 进行筛选,并保留满足条件的元素,示例:

1
2
val map = Map(1 -> "zhangsan", 2 -> "lisi", 3 -> "wanger")
map.filterKeys(_ % 2 == 0) // 输出:Map(2 -> lisi)

7.2 mapValues & transform

函数 mapValues 和 transform 均用于对 Map 对象的值进行转换,区别在于函数 mapValues 的入参只有 value,而 transform 的入参除了 value,还包含 key,示例:

1
2
3
val map = Map(1 -> "zhangsan", 2 -> "lisi", 3 -> "wanger")
map.mapValues(_.reverse) // 输出:Map(1 -> nasgnahz, 2 -> isil, 3 -> regnaw)
map.transform((k, v) => v + k) // 输出:Map(1 -> zhangsan1, 2 -> lisi2, 3 -> wanger3)

对于可变 Map 而言,因为是原地转换,所以要求 transform 操作输出的 value 类型与输入的类型相同。

八. 反转操作

对于给定的 Map 集合,我们可以使用 map 函数很容易将 key 和 value 反转,但是如果 value 存在重复,那么这个时候我们可能希望反转之后的 value 是一个集合类型,这种需求该如何实现?

1
2
3
4
5
6
7
8
// value 不重复
val map = Map(1 -> "zhangsan", 2 -> "lisi", 3 -> "wanger")
map.map(t => (t._2, t._1)) // 输出:Map(zhangsan -> 1, lisi -> 2, wanger -> 3)
map.map { case (x, y) => (y, x) } // 输出:Map(zhangsan -> 1, lisi -> 2, wanger -> 3)

// value 重复
val map2 = Map("a" -> 1, "b" -> 2, "c" -> 2, "d" -> 3, "e" -> 1)
map2.groupBy(_._2).mapValues(_.keys.toList) // 输出:Map(2 -> List(b, c), 1 -> List(e, a), 3 -> List(d))

上述示例中重复 value 的反转执行流程如下:

  1. 对包含重复 value 的 Map 执行 groupBy 操作:Map(2 -> Map(b -> 2, c -> 2), 1 -> Map(e -> 1, a -> 1), 3 -> Map(d -> 3))
  2. 对步骤 1 的结果执行 mapValues 操作,提取 key,并封装成 List 集合。

参考