Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

构建映射

Map 是非常有用的编程工具,有许多方法可以构建它们。

为了创建可重复的数据集,我们使用了 Manipulating Lists 中展示的技术,其中两个 List 被合并在一起,然后结果在一个 lambda 中被用于调用构造函数,生成一个 List<Person>

// BuildingMaps/People.kt
package buildingmaps

data class Person(
  val name: String,
  val age: Int
)

val names = listOf("Alice", "Arthricia",
  "Bob", "Bill", "Birdperson", "Charlie",
  "Crocubot", "Franz", "Revolio")

val ages = listOf(21, 15, 25, 25, 42, 21,
  42, 21, 33)

fun people(): List<Person> =
  names.zip(ages) { name, age ->
    Person(name, age)
  }

Map 使用键来快速访问其值。通过使用 age 作为键构建一个 Map,我们可以通过年龄快速查找人的分组。库函数 groupBy() 是一种创建这种 Map 的方式:

// BuildingMaps/GroupBy.kt
import buildingmaps.*
import atomictest.eq

fun main() {
  val map: Map<Int, List<Person>> =
    people().groupBy(Person::age)
  map[15] eq listOf(Person("Arthricia", 15))
  map[21] eq listOf(
    Person("Alice", 21),
    Person("Charlie", 21),
    Person("Franz", 21))
  map[22] eq null
  map[25] eq listOf(
    Person("Bob", 25),
    Person("Bill", 25))
  map[33] eq listOf(Person("Revolio", 33))
  map[42] eq listOf(
    Person("Birdperson", 42),
    Person("Crocubot", 42))
}

groupBy() 的参数产生一个 Map,其中每个键连接到一个 List 元素。在这里,所有相同 age 的人都被 age 键选择。

您也可以使用 filter() 函数产生相同的分组,但是 groupBy() 更可取,因为它只执行一次分组。使用 filter() 您必须为每个新键重复分组:

// BuildingMaps/GroupByVsFilter.kt
import buildingmaps.*
import atomictest.eq

fun main() {
  val groups =
    people().groupBy { it.name.first() }
  // groupBy() 产生具有映射速度的访问:
  groups['A'] eq listOf(Person("Alice", 21),
    Person("Arthricia", 15))
  groups['Z'] eq null

  // 必须为每个字符重复使用 filter():
  people().filter {
    it.name.first() == 'A'
  } eq listOf(Person("Alice", 21),
    Person("Arthricia", 15))
  people().filter {
    it.name.first() == 'F'
  } eq listOf(Person("Franz", 21))

  people().partition {
    it.name.first() == 'A'
  } eq Pair(
    listOf(Person("Alice", 21),
      Person("Arthricia", 15)),
    listOf(Person("Bob", 25),
      Person("Bill", 25),
      Person("Birdperson", 42),
      Person("Charlie", 21),
      Person("Crocubot", 42),
      Person("Franz", 21),
      Person("Revolio", 33)))
}

在这里,groupBy()people() 按其首字母分组,由 first() 选择。我们还可以使用 filter() 通过为每个字符重复使用 lambda 代码来产生相同的结果。

如果您只需要两个分组,则 partition() 函数更直接,因为它基于谓词将内容分为两个列表。当您需要超过两个结果分组时,groupBy() 是适当的。

associateWith() 允许您获取一组键,并通过将每个键与其参数(在这里是 lambda)关联起来来构建一个 Map

// BuildingMaps/AssociateWith.kt
import buildingmaps.*
import atomictest.eq

fun main() {
  val map: Map<Person, String> =
    people().associateWith { it.name }
  map eq mapOf(
    Person("Alice", 21) to "Alice",
    Person("Arthricia", 15) to "Arthricia",
    Person("Bob", 25) to "Bob",
    Person("Bill", 25) to "Bill",
    Person("Birdperson", 42) to "Birdperson",
    Person("Charlie", 21) to "Charlie",
    Person("Crocubot", 42) to "Crocubot",
    Person("Franz", 21) to "Franz",
    Person("Revolio", 33) to "Revolio")
}

associateBy() 反转了 associateWith() 产生的关联顺序 - 选择器(下面的 lambda)变为键:

// BuildingMaps/AssociateBy.kt
import buildingmaps.*
import atomictest.eq

fun main() {
  val map: Map<String, Person> =
   

 people().associateBy { it.name }
  map eq mapOf(
    "Alice" to Person("Alice", 21),
    "Arthricia" to Person("Arthricia", 15),
    "Bob" to Person("Bob", 25),
    "Bill" to Person("Bill", 25),
    "Birdperson" to Person("Birdperson", 42),
    "Charlie" to Person("Charlie", 21),
    "Crocubot" to Person("Crocubot", 42),
    "Franz" to Person("Franz", 21),
    "Revolio" to Person("Revolio", 33))
}

associateBy() 必须与唯一的选择键一起使用,并返回一个将每个唯一键与由该键选择的单个元素配对的 Map

// BuildingMaps/AssociateByUnique.kt
import buildingmaps.*
import atomictest.eq

fun main() {
  // 当键不是唯一时,associateBy() 会失败 - 值会消失:
  val ages = people().associateBy { it.age }
  ages eq mapOf(
    21 to Person("Franz", 21),
    15 to Person("Arthricia", 15),
    25 to Person("Bill", 25),
    42 to Person("Crocubot", 42),
    33 to Person("Revolio", 33))
}

如果谓词选择了多个值,就像 ages 中一样,只有最后一个值会出现在生成的 Map 中。

getOrElse() 尝试在 Map 中查找值。与它关联的 lambda 在没有键时计算默认值。因为它是一个 lambda,所以只在必要时才计算默认的 key

// BuildingMaps/GetOrPut.kt
import atomictest.eq

fun main() {
  val map = mapOf(1 to "one", 2 to "two")

  map.getOrElse(0) { "zero" } eq "zero"

  val mutableMap = map.toMutableMap()
  mutableMap.getOrPut(0) { "zero" } eq
    "zero"
  mutableMap eq "{1=one, 2=two, 0=zero}"
}

getOrPut() 适用于 MutableMap。如果键存在,它只返回与之关联的值。如果未找到键,它会计算该值,将其放入映射中并返回该值。

许多 Map 操作都与 List 中的操作相似。例如,您可以对 Map 的内容进行 filter()map()。您可以分别筛选键和值:

// BuildingMaps/FilterMap.kt
import atomictest.eq

fun main() {
  val map = mapOf(1 to "one",
    2 to "two", 3 to "three", 4 to "four")

  map.filterKeys { it % 2 == 1 } eq
    "{1=one, 3=three}"

  map.filterValues { it.contains('o') } eq
    "{1=one, 2=two, 4=four}"

  map.filter { entry ->
    entry.key % 2 == 1 &&
      entry.value.contains('o')
  } eq "{1=one}"
}

这三个函数 filter()filterKeys()filterValues() 会产生一个新的 Map,其中只包含满足谓词的元素。filterKeys() 将其谓词应用于键,而 filterValues() 则将其谓词应用于值。

对 Map 应用操作

在 Map 中应用 map() 听起来像一种自反的说法,就像说“盐是咸的一样”。单词 map 代表了两个不同的概念:

  • 转换集合
  • 键-值数据结构

在许多编程语言中,单词 map 被用于这两个概念。为了清楚起见,当将 map() 应用于 Map 时,我们说 transform a map

在这里,我们演示了 map()mapKeys()mapValues()

// BuildingMaps/TransformingMap.kt
import atomictest.eq

fun main() {
  val even = mapOf(2 to "two", 4 to "four")
  even.map {                            // [1]
    "${it.key}=${it.value}"
  } eq listOf("2=two", "4=four")

  even

.map { (key, value) ->            // [2]
    "$key=$value"
  } eq listOf("2=two", "4=four")

  even.mapKeys { (num, _) -> -num }     // [3]
    .mapValues { (_, str) -> "minus $str" } eq
    mapOf(-2 to "minus two",
      -4 to "minus four")

  even.map { (key, value) ->
    -key to "minus $value"
  }.toMap() eq mapOf(-2 to "minus two", // [4]
    -4 to "minus four")
}
  • [1] 在这里,map() 接受一个带有 Map.Entry 参数的谓词。我们将其内容作为 it.keyit.value 访问。
  • [2] 您还可以使用 析构声明 将条目内容放入 keyvalue 中。
  • [3] 如果未使用参数,则下划线 (_) 可避免编译器投诉。mapKeys()mapValues() 返回一个新的 Map,其中所有键或值都相应地进行了转换。
  • [4] map() 返回一组配对,因此要生成一个 Map,我们使用显式转换 toMap()

any()all() 这样的函数也可以应用于 Map

// BuildingMaps/SimilarOperation.kt
import atomictest.eq

fun main() {
  val map = mapOf(1 to "one",
    -2 to "minus two")
  map.any { (key, _) -> key < 0 } eq true
  map.all { (key, _) -> key < 0 } eq false
  map.maxByOrNull { it.key }?.value eq "one"
}

any() 检查 Map 中的任何条目是否满足给定的谓词,而 all() 仅在 Map 中的所有条目都满足谓词时为 true

maxByOrNull() 根据给定的标准查找最大的条目。可能不存在最大的条目,因此结果可为空。

练习和解答可以在 www.AtomicKotlin.com 找到。