集合转换

Kotlin 标准库为集合 转换 提供了一组扩展函数。 这些函数根据提供的转换规则从现有集合中构建新集合。 在此页面中,我们将概述可用的集合转换函数。

映射

映射 转换从另一个集合的元素上的函数结果创建一个集合。 基本的映射函数是 map()。 它将给定的 lambda 函数应用于每个后续元素,并返回 lambda 结果列表。 结果的顺序与元素的原始顺序相同。 如需应用还要用到元素索引作为参数的转换,请使用 mapIndexed()

  1. fun main() {
  2. //sampleStart
  3. val numbers = setOf(1, 2, 3)
  4. println(numbers.map { it * 3 })
  5. println(numbers.mapIndexed { idx, value -> value * idx })
  6. //sampleEnd
  7. }

如果转换在某些元素上产生 null 值,则可以通过调用 mapNotNull() 函数取代 map()mapIndexedNotNull() 取代 mapIndexed() 来从结果集中过滤掉 null 值。

  1. fun main() {
  2. //sampleStart
  3. val numbers = setOf(1, 2, 3)
  4. println(numbers.mapNotNull { if ( it == 2) null else it * 3 })
  5. println(numbers.mapIndexedNotNull { idx, value -> if (idx == 0) null else value * idx })
  6. //sampleEnd
  7. }

映射转换时,有两个选择:转换键,使值保持不变,反之亦然。 要将指定转换应用于键,请使用 mapKeys();反过来,mapValues() 转换值。 这两个函数都使用将映射条目作为参数的转换,因此可以操作其键与值。

  1. fun main() {
  2. //sampleStart
  3. val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
  4. println(numbersMap.mapKeys { it.key.toUpperCase() })
  5. println(numbersMap.mapValues { it.value + it.key.length })
  6. //sampleEnd
  7. }

合拢

合拢 转换是根据两个集合中具有相同位置的元素构建配对。 在 Kotlin 标准库中,这是通过 zip() 扩展函数完成的。 在一个集合(或数组)上以另一个集合(或数组)作为参数调用时,zip() 返回 Pair 对象的列表(List)。 接收者集合的元素是这些配对中的第一个元素。 如果集合的大小不同,则 zip() 的结果为较小集合的大小;结果中不包含较大集合的后续元素。 zip() 也可以中缀形式调用 a zip b

  1. fun main() {
  2. //sampleStart
  3. val colors = listOf("red", "brown", "grey")
  4. val animals = listOf("fox", "bear", "wolf")
  5. println(colors zip animals)
  6. val twoAnimals = listOf("fox", "bear")
  7. println(colors.zip(twoAnimals))
  8. //sampleEnd
  9. }

也可以使用带有两个参数的转换函数来调用 zip():接收者元素和参数元素。 在这种情况下,结果 List 包含在具有相同位置的接收者对和参数元素对上调用的转换函数的返回值。

  1. fun main() {
  2. //sampleStart
  3. val colors = listOf("red", "brown", "grey")
  4. val animals = listOf("fox", "bear", "wolf")
  5. println(colors.zip(animals) { color, animal -> "The ${animal.capitalize()} is $color"})
  6. //sampleEnd
  7. }

当拥有 PairList 时,可以进行反向转换 unzipping——从这些键值对中构建两个列表:

  • 第一个列表包含原始列表中每个 Pair 的键。
  • 第二个列表包含原始列表中每个 Pair 的值。

要分割键值对列表,请调用 unzip()

  1. fun main() {
  2. //sampleStart
  3. val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
  4. println(numberPairs.unzip())
  5. //sampleEnd
  6. }

关联

关联 转换允许从集合元素和与其关联的某些值构建 Map。 在不同的关联类型中,元素可以是关联 Map 中的键或值。

基本的关联函数 associateWith() 创建一个 Map,其中原始集合的元素是键,并通过给定的转换函数从中产生值。 如果两个元素相等,则仅最后一个保留在 Map 中。

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four")
  4. println(numbers.associateWith { it.length })
  5. //sampleEnd
  6. }

为了使用集合元素作为值来构建 Map,有一个函数 associateBy()。 它需要一个函数,该函数根据元素的值返回键。如果两个元素相等,则仅最后一个保留在 Map 中。 还可以使用值转换函数来调用 associateBy()

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four")
  4. println(numbers.associateBy { it.first().toUpperCase() })
  5. println(numbers.associateBy(keySelector = { it.first().toUpperCase() }, valueTransform = { it.length }))
  6. //sampleEnd
  7. }

另一种构建 Map 的方法是使用函数 associate(),其中 Map 键和值都是通过集合元素生成的。 它需要一个 lambda 函数,该函数返回 Pair:键和相应 Map 条目的值。

请注意,associate() 会生成临时的 Pair 对象,这可能会影响性能。 因此,当性能不是很关键或比其他选项更可取时,应使用 associate()

后者的一个示例:从一个元素一起生成键和相应的值。

  1. fun main() {
  2. data class FullName (val firstName: String, val lastName: String)
  3. fun parseFullName(fullName: String): FullName {
  4. val nameParts = fullName.split(" ")
  5. if (nameParts.size == 2) {
  6. return FullName(nameParts[0], nameParts[1])
  7. } else throw Exception("Wrong name format")
  8. }
  9. //sampleStart
  10. val names = listOf("Alice Adams", "Brian Brown", "Clara Campbell")
  11. println(names.associate { name -> parseFullName(name).let { it.lastName to it.firstName } })
  12. //sampleEnd
  13. }

此时,首先在一个元素上调用一个转换函数,然后根据该函数结果的属性建立 Pair。

打平

如需操作嵌套的集合,则可能会发现提供对嵌套集合元素进行打平访问的标准库函数很有用。

第一个函数为 flatten()。可以在一个集合的集合(例如,一个 Set 组成的 List)上调用它。 该函数返回嵌套集合中的所有元素的一个 List

  1. fun main() {
  2. //sampleStart
  3. val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(1, 2))
  4. println(numberSets.flatten())
  5. //sampleEnd
  6. }

另一个函数——flatMap() 提供了一种灵活的方式来处理嵌套的集合。 它需要一个函数将一个集合元素映射到另一个集合。 因此,flatMap() 返回单个列表其中包含所有元素的值。 所以,flatMap() 表现为 map()(以集合作为映射结果)与 flatten() 的连续调用。

  1. data class StringContainer(val values: List<String>)
  2. fun main() {
  3. //sampleStart
  4. val containers = listOf(
  5. StringContainer(listOf("one", "two", "three")),
  6. StringContainer(listOf("four", "five", "six")),
  7. StringContainer(listOf("seven", "eight"))
  8. )
  9. println(containers.flatMap { it.values })
  10. //sampleEnd
  11. }

字符串表示

如果需要以可读格式检索集合内容,请使用将集合转换为字符串的函数:joinToString()joinTo()

joinToString() 根据提供的参数从集合元素构建单个 StringjoinTo() 执行相同的操作,但将结果附加到给定的 Appendable 对象。

当使用默认参数调用时,函数返回的结果类似于在集合上调用 toString():各元素的字符串表示形式以空格分隔而成的 String

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four")
  4. println(numbers)
  5. println(numbers.joinToString())
  6. val listString = StringBuffer("The list of numbers: ")
  7. numbers.joinTo(listString)
  8. println(listString)
  9. //sampleEnd
  10. }

要构建自定义字符串表示形式,可以在函数参数 separatorprefixpostfix中指定其参数。 结果字符串将以 prefix 开头,以 postfix 结尾。除最后一个元素外,separator 将位于每个元素之后。

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four")
  4. println(numbers.joinToString(separator = " | ", prefix = "start: ", postfix = ": end"))
  5. //sampleEnd
  6. }

对于较大的集合,可能需要指定 limit ——将包含在结果中元素的数量。 如果集合大小超出 limit,所有其他元素将被 truncated 参数的单个值替换。

  1. fun main() {
  2. //sampleStart
  3. val numbers = (1..100).toList()
  4. println(numbers.joinToString(limit = 10, truncated = "<...>"))
  5. //sampleEnd
  6. }

最后,要自定义元素本身的表示形式,请提供 transform 函数。

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four")
  4. println(numbers.joinToString { "Element: ${it.toUpperCase()}"})
  5. //sampleEnd
  6. }