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

集合操作

函数式编程语言的一个重要特性是能够轻松地对对象的集合执行批量操作。

大多数函数式编程语言都提供了强大的集合处理功能,Kotlin也不例外。你已经见过map()filter()any()forEach()。这个小节介绍了List和其他集合类型可用的其他操作。

我们首先来看看不同的方法来创建List。这里,我们使用Lambda来初始化List

// OperationsOnCollections/CreatingLists.kt
import atomictest.eq

fun main() {
  // Lambda参数是元素的索引:
  val list1 = List(10) { it }
  list1 eq "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"

  // 单一值的列表:
  val list2 = List(10) { 0 }
  list2 eq "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]"

  // 字母列表:
  val list3 = List(10) { 'a' + it }
  list3 eq "[a, b, c, d, e, f, g, h, i, j]"

  // 循环遍历一个序列:
  val list4 = List(10) { list3[it % 3] }
  list4 eq "[a, b, c, a, b, c, a, b, c, a]"
}

这个版本的List构造函数有两个参数:List的大小和一个初始化每个List元素的Lambda(元素索引作为it参数传递)。记住,如果Lambda是最后一个参数,它可以与参数列表分开。

MutableList可以以同样的方式进行初始化。在这里,我们可以看到初始化的Lambda既在参数列表内部(mutableList1),也与参数列表分开(mutableList2):

// OperationsOnCollections/ListInit.kt
import atomictest.eq

fun main() {
  val mutableList1 =
    MutableList(5, { 10 * (it + 1) })
  mutableList1 eq "[10, 20, 30, 40, 50]"
  val mutableList2 =
    MutableList(5) { 10 * (it + 1) }
  mutableList2 eq "[10, 20, 30, 40, 50]"
}

请注意,List()MutableList()不是构造函数,而是函数。它们的名称故意以大写字母开头,以使它们看起来像构造函数。

许多集合函数采用谓词,并将其与集合的元素进行测试,其中一些我们已经见过:

  • filter() 生成一个包含与给定谓词匹配的所有元素的列表。
  • any() 如果至少有一个元素与谓词匹配,则返回true
  • all() 检查是否所有元素都与谓词匹配。
  • none() 检查是否没有元素与谓词匹配。
  • find()firstOrNull() 都返回与谓词匹配的第一个元素,如果没有找到此类元素则返回null
  • lastOrNull() 返回与谓词匹配的最后一个元素,或者null
  • count() 返回与谓词匹配的元素数量。

以下是每个函数的简单示例:

// OperationsOnCollections/Predicates.kt
import atomictest.eq

fun main() {
  val list = listOf(-3, -1, 5, 7, 10)

  list.filter { it > 0 } eq listOf(5, 7, 10)
  list.count { it > 0 } eq 3

  list.find { it > 0 } eq 5
  list.firstOrNull { it > 0 } eq 5
  list.lastOrNull { it < 0 } eq -1

  list.any { it > 0 } eq true
  list.any { it != 0 } eq true

  list.all { it > 0 } eq false
  list.all { it != 0 } eq true

  list.none { it > 0 } eq false
  list.none { it == 0 } eq true
}

filter()count() 对每个元素应用谓词,而 any()find() 在找到第一个匹配结果时停止。例如,如果第一个元素满足谓词,any()会立即返回true,而find()会返回第一个匹配的元素。只有当列表中不包含与给定谓词匹配的元素时,所有元素都会被处理。

filter() 返回满足给定谓词的一组元素。有时您可能对剩余的组感兴趣 - 即不满足谓词的元素。filterNot() 会产生这个剩余的组,但是partition() 可能更有用,因为它同时生成了这两个列表:

// OperationsOnCollections/Partition.kt
import atomictest.eq

fun main() {
  val list = listOf(-3, -1, 5, 7, 10)
  val isPositive = { i: Int -> i > 0 }

  list.filter(isPositive) eq "[5, 7, 10]"
  list.filterNot(isPositive) eq "[-3, -1]"

  val (pos, neg) = list.partition { it > 0 }
  pos eq "[5, 7, 10]"
  neg eq "[-3, -1]"
}

partition() 产生一个包含 ListPair 对象。使用Destructuring Declarations,您可以将 Pair 的元素分配给用括号括起的 varval 组。Destructuring 是指同时定义多个 varval 并从赋值的右侧表达式进行初始化。在这里,析构用于自定义函数:

// OperationsOnCollections/PairOfLists.kt
package operationsoncollections
import atomictest.eq

fun createPair() = Pair(1, "one")

fun main() {
  val (i, s) = createPair()
  i eq 1
  s eq "one"
}

filterNotNull() 生成一个移除了 null 的新的 List

// OperationsOnCollections/FilterNotNull.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, null)
  list.filterNotNull() eq "[1, 2]"
}

Lists中,我们看到了一些应用于可比较元素列表的函数,比如sum()sorted()。这些函数不能应用于非可加或非可比元素的列表,但它们有名为sumBy()sortedBy()的对应函数。您将一个函数(通常是一个Lambda)作为参数传递,该函数指定用于操作的属性:

// OperationsOnCollections/ByOperations.kt
package operationsoncollections
import atomictest.eq

data class Product(
  val description: String,
  val price: Double
)

fun main() {
  val products = listOf(
    Product("bread", 2.0),
    Product("wine", 5.0)
  )
  products.sumByDouble { it.price } eq 7.0

  products.sortedByDescending { it.price } eq
    "[Product(description=wine, price=5.0)," +
    " Product(description=bread, price=2.0)]"
  products.minByOrNull { it.price } eq
    Product("bread", 2.0)
}

请注意,我们有两个函数sumBy()sumByDouble(),分别用于对整数值和双精度值进行求和。sorted()sortedBy()对集合进行升序排序,而sortedDescending()sortedByDescending()对集合进行降序排序。

minByOrNull基于给定的条件返回一个最小值,如果列表为空,则返回null

take()drop()分别产生或移除第一个元素,而takeLast()dropLast()分别产生或移除最后一个元素。这些函数还有接受谓词的对应函数,用于指定要获取或删除的元素:

// OperationsOnCollections/TakeOrDrop.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'X', 'Z')
  list.takeLast(3) eq "[c, X, Z]"
  list.takeLastWhile { it.isUpperCase() } eq
    "[X, Z]"
  list.drop(1) eq "[b, c, X, Z]"
  list.dropWhile { it.isLowerCase() } eq
    "[X, Z]"
}

与您在List中看到的操作一样,也可以在Set上进行操作:

// OperationsOnCollections/SetOperations.kt
import atomictest.eq

fun main() {
  val set = setOf("a", "ab", "ac")
  set.maxByOrNull { it.length }?.length eq 2
  set.filter {
    it.contains('b')
  } eq listOf("ab")
  set.map { it.length } eq listOf(1, 2, 2)
}

maxByOrNull() 如果集合为空,则返回null,因此其结果是可空的。

请注意,将filter()map()应用于Set时,它们将其结果返回为List

filter() 返回满足给定谓词的一组元素。有时您可能对剩余的组感兴趣 - 即不满足谓词的元素。filterNot() 会产生这个剩余的组,但是partition() 可能更有用,因为它同时生成了这两个列表:

// OperationsOnCollections/Partition.kt
import atomictest.eq

fun main() {
  val list = listOf(-3, -1, 5, 7, 10)
  val isPositive = { i: Int -> i > 0 }

  list.filter(isPositive) eq "[5, 7, 10]"
  list.filterNot(isPositive) eq "[-3, -1]"

  val (pos, neg) = list.partition { it > 0 }
  pos eq "[5, 7, 10]"
  neg eq "[-3, -1]"
}

partition() 产生一个包含 ListPair 对象。使用Destructuring Declarations,您可以将 Pair 的元素分配给用括号括起的 varval 组。Destructuring 是指同时定义多个 varval 并从赋值的右侧表达式进行初始化。在这里,析构用于自定义函数:

// OperationsOnCollections/PairOfLists.kt
package operationsoncollections
import atomictest.eq

fun createPair() = Pair(1, "one")

fun main() {
  val (i, s) = createPair()
  i eq 1
  s eq "one"
}

filterNotNull() 生成一个移除了 null 的新的 List

// OperationsOnCollections/FilterNotNull.kt
import atomictest.eq

fun main() {
  val list = listOf(1, 2, null)
  list.filterNotNull() eq "[1, 2]"
}

Lists中,我们看到了一些应用于可比较元素列表的函数,比如sum()sorted()。这些函数不能应用于非可加或非可比元素的列表,但它们有名为sumBy()sortedBy()的对应函数。您将一个函数(通常是一个Lambda)作为参数传递,该函数指定用于操作的属性:

// OperationsOnCollections/ByOperations.kt
package operationsoncollections
import atomictest.eq

data class Product(
  val description: String,
  val price: Double
)

fun main() {
  val products = listOf(
    Product("bread", 2.0),
    Product("wine", 5.0)
  )
  products.sumByDouble { it.price } eq 7.0

  products.sortedByDescending { it.price } eq
    "[Product(description=wine, price=5.0)," +
    " Product(description=bread, price=2.0)]"
  products.minByOrNull { it.price } eq
    Product("bread", 2.0)
}

请注意,我们有两个函数sumBy()sumByDouble(),分别用于对整数值和双精度值进行求和。sorted()sortedBy()对集合进行升序排序,而sortedDescending()sortedByDescending()对集合进行降序排序。

minByOrNull基于给定的条件返回一个最小值,如果列表为空,则返回null

take()drop()分别产生或移除第一个元素,而takeLast()dropLast()分别产生或移除最后一个元素。这些函数还有接受谓词的对应函数,用于指定要获取或删除的元素:

// OperationsOnCollections/TakeOrDrop.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'X', 'Z')
  list.takeLast(3) eq "[c, X, Z]"
  list.takeLastWhile { it.isUpperCase() } eq
    "[X, Z]"
  list.drop(1) eq "[b, c, X, Z]"
  list.dropWhile { it.isLowerCase() } eq
    "[X, Z]"
}

与您在List中看到的操作一样,也可以在Set上进行操作:

// OperationsOnCollections/SetOperations.kt
import atomictest.eq

fun main() {
  val set = setOf("a", "ab", "ac")
  set.maxByOrNull { it.length }?.length eq 2
  set.filter {
    it.contains('b')
  } eq listOf("ab")
  set.map { it.length } eq listOf(1, 2, 2)
}

maxByOrNull() 如果集合为空,则返回null,因此其结果是可空的。

请注意,将filter()map()应用于Set时,它们将其结果返回为List

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