Kotlin集合的操纵(3)
九路 150 3

集合聚合操作

Kotlin 集合包含用于常用的 聚合操作 (基于集合内容返回单个值的操作)的函数 。 其中大多数是众所周知的,并且其工作方式与在其他语言中相同。

  • min() 与 max() 分别返回最小和最大的元素;
  • average() 返回数字集合中元素的平均值;
  • sum() 返回数字集合中元素的总和;
  • count() 返回集合中元素的数量;
    val numbers = listOf(6, 42, 10, 4)
    ​
    println("Count: ${numbers.count()}")
    println("Max: ${numbers.max()}")
    println("Min: ${numbers.min()}")
    println("Average: ${numbers.average()}")
    println("Sum: ${numbers.sum()}")
    还有一些通过某些选择器函数或自定义 Comparator 来检索最小和最大元素的函数。

maxBy()/minBy() 接受一个选择器函数并返回使选择器返回最大或最小值的元素。 maxWith()/minWith() 接受一个 Comparator 对象并且根据此 Comparator 对象返回最大或最小元素。

val min3Remainder = numbers.minBy { it % 3 }
println(min3Remainder)
​
val strings = listOf("one", "two", "three", "four")
val longestString = strings.maxWith(compareBy { it.length })
println(longestString)

此外,有一些高级的求和函数,它们接受一个函数并返回对所有元素调用此函数的返回值的总和:

  • sumBy() 使用对集合元素调用返回 Int 值的函数。
  • sumByDouble() 与返回 Double 的函数一起使用。 ​
    println(numbers.sumBy { it * 2 })
    println(numbers.sumByDouble { it.toDouble() / 2 })

    Fold 与 reduce

    对于更特定的情况,有函数 reduce() 和 fold(),它们依次将所提供的操作应用于集合元素并返回累积的结果。 操作有两个参数:先前的累积值和集合元素。

这两个函数的区别在于:fold() 接受一个初始值并将其用作第一步的累积值,而 reduce() 的第一步则将第一个和第二个元素作为第一步的操作参数。

​
val sum = numbers.reduce { sum, element -> sum + element }
println(sum)
val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 }
println(sumDoubled)
​
//val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 } //错误:第一个元素在结果中没有加倍
//println(sumDoubledReduce)

上面的实例展示了区别:fold() 用于计算加倍的元素之和。 如果将相同的函数传给 reduce(),那么它会返回另一个结果,因为在第一步中它将列表的第一个和第二个元素作为参数,所以第一个元素不会被加倍。

如需将函数以相反的顺序应用于元素,可以使用函数 reduceRight() 和 foldRight() 它们的工作方式类似于 fold()reduce(),但从最后一个元素开始,然后再继续到前一个元素。 记住,在使用 foldRight 或 reduceRight 时,操作参数会更改其顺序:第一个参数变为元素,然后第二个参数变为累积值。

val numbers = listOf(5, 2, 10, 4)
val sumDoubledRight = numbers.foldRight(0) { element, sum -> sum + element * 2 }
println(sumDoubledRight)

你还可以使用将元素索引作为参数的操作。 为此,使用函数 reduceIndexed() 和 foldIndexed() 传递元素索引作为操作的第一个参数。

最后,还有将这些操作从右到左应用于集合元素的函数——reduceRightIndexed() 与 foldRightIndexed()。

val numbers = listOf(5, 2, 10, 4)
val sumEven = numbers.foldIndexed(0) { idx, sum, element -> if (idx % 2 == 0) sum + element else sum }
println(sumEven)
​
val sumEvenRight = numbers.foldRightIndexed(0) { idx, element, sum -> if (idx % 2 == 0) sum + element else sum }
println(sumEvenRight)

All reduce operations throw an exception on empty collections. To receive null instead, use their *OrNull() counterparts:

  • reduceOrNull()
  • reduceRightOrNull()
  • reduceIndexedOrNull()
  • reduceRightIndexedOrNull()

集合写操作

可变集合支持更改集合内容的操作,例如添加或删除元素。 在此页面上,我们将描述实现 MutableCollection 的所有写操作。 有关 ListMap 可用的更多特定操作,请分别参见 List 相关操作与 Map 相关操作。

添加元素

要将单个元素添加到列表或集合,请使用 add() 函数。指定的对象将添加到集合的末尾。

val numbers = mutableListOf(1, 2, 3, 4)
numbers.add(5)
println(numbers)

addAll() 将参数对象的每个元素添加到列表或集合中。参数可以是 IterableSequenceArray。 接收者的类型和参数可能不同,例如,你可以将所有内容从 Set添加到 List

当在列表上调用时,addAll() 会按照在参数中出现的顺序添加各个新元素。 你也可以调用 addAll() 时指定一个元素位置作为第一参数。 参数集合的第一个元素会被插入到这个位置。 其他元素将跟随在它后面,将接收者元素移到末尾。

val numbers = mutableListOf(1, 2, 5, 6)
numbers.addAll(arrayOf(7, 8))
println(numbers)
numbers.addAll(2, setOf(3, 4))
println(numbers)

你还可以使用 plus 运算符 - plusAssign (+=) 添加元素。 当应用于可变集合时,+= 将第二个操作数(一个元素或另一个集合)追加到集合的末尾。

val numbers = mutableListOf("one", "two")
numbers += "three"
println(numbers)
numbers += listOf("four", "five")    
println(numbers)

删除元素

若要从可变集合中移除元素,请使用 remove() 函数。 remove() 接受元素值,并删除该值的一个匹配项。

val numbers = mutableListOf(1, 2, 3, 4, 3)
numbers.remove(3)                    // 删除了第一个 `3`
println(numbers)
numbers.remove(5)                    // 什么都没删除
println(numbers)

要一次删除多个元素,有以下函数:

  • removeAll() 移除参数集合中存在的所有元素。 或者,你可以用谓词作为参数来调用它;在这种情况下,函数移除谓词产生 true 的所有元素。
  • retainAll() 与 removeAll() 相反:它移除除参数集合中的元素之外的所有元素。 当与谓词一起使用时,它只留下与之匹配的元素。
  • clear() 从列表中移除所有元素并将其置空。
    val numbers = mutableListOf(1, 2, 3, 4)
    println(numbers)
    numbers.retainAll { it >= 3 }
    println(numbers)
    numbers.clear()
    println(numbers)
    ​
    val numbersSet = mutableSetOf("one", "two", "three", "four")
    numbersSet.removeAll(setOf("one", "two"))
    println(numbersSet)
    从集合中移除元素的另一种方法是使用 minusAssign (-=) ——原地修改版的 minus 操作符。 minus 操作符。 第二个参数可以是元素类型的单个实例或另一个集合。 右边是单个元素时,-= 会移除它的第一个匹配项。 反过来,如果它是一个集合,那么它的所有元素的每次出现都会删除。 例如,如果列表包含重复的元素,它们将被同时删除。 第二个操作数可以包含集合中不存在的元素。这些元素不会影响操作的执行。
    val numbers = mutableListOf("one", "two", "three", "three", "four")
    numbers -= "three"
    println(numbers)
    numbers -= listOf("four", "five")    
    //numbers -= listOf("four")    // 与上述相同
    println(numbers)    

    更新元素

    list 和 map 还提供更新元素的操作。 它们在 List 相关操作与 Map 相关操作中有所描述。 对于 set 来说,更新没有意义,因为它实际上是移除一个元素并添加另一个元素。

List 相关操作

List 是 Kotlin 标准库中最受欢迎的集合类型。对列表元素的索引访问为 List 提供了一组强大的操作。

按索引取元素

List 支持按索引取元素的所有常用操作: elementAt()first()last() 与取单个元素中列出的其他操作。 List 的特点是能通过索引访问特定元素,因此读取元素的最简单方法是按索引检索它。 这是通过 get() 函数或简写语法 [index] 来传递索引参数完成的。

如果 List 长度小于指定的索引,则抛出异常。 另外,还有两个函数能避免此类异常:

  • getOrElse() 提供用于计算默认值的函数,如果集合中不存在索引,则返回默认值。

  • getOrNull() 返回 null 作为默认值。

    val numbers = listOf(1, 2, 3, 4)
    println(numbers.get(0))
    println(numbers[0])
    //numbers.get(5)                         // exception!
    println(numbers.getOrNull(5))             // null
    println(numbers.getOrElse(5, {it}))        // 5

    取列表的一部分

    除了取集合的一部分中常用的操作, List 还提供 subList() 该函数将指定元素范围的视图作为列表返回。 因此,如果原始集合的元素发生变化,则它在先前创建的子列表中也会发生变化,反之亦然。

    val numbers = (0..13).toList()
    println(numbers.subList(3, 6))

    查找元素位置

    线性查找

    在任何列表中,都可以使用 indexOf() 或 lastIndexOf() 函数找到元素的位置。 它们返回与列表中给定参数相等的元素的第一个或最后一个位置。 如果没有这样的元素,则两个函数均返回 -1。

    val numbers = listOf(1, 2, 3, 4, 2, 5)
    println(numbers.indexOf(2))
    println(numbers.lastIndexOf(2))

    还有一对函数接受谓词并搜索与之匹配的元素:

  • indexOfFirst() 返回与谓词匹配的第一个元素的索引,如果没有此类元素,则返回 -1

  • indexOfLast() 返回与谓词匹配的最后一个元素的索引,如果没有此类元素,则返回 -1

    val numbers = mutableListOf(1, 2, 3, 4)
    println(numbers.indexOfFirst { it > 2})
    println(numbers.indexOfLast { it % 2 == 1})

    在有序列表中二分查找

    还有另一种搜索列表中元素的方法——二分查找算法。 它的工作速度明显快于其他内置搜索功能,但要求该列表按照一定的顺序(自然排序或函数参数中提供的另一种排序)按升序排序过。 否则,结果是不确定的。

要搜索已排序列表中的元素,请调用 binarySearch() 函数,并将该值作为参数传递。 如果存在这样的元素,则函数返回其索引;否则,将返回 (-insertionPoint - 1),其中 insertionPoint 为应插入此元素的索引,以便列表保持排序。 如果有多个具有给定值的元素,搜索则可以返回其任何索引。

还可以指定要搜索的索引区间:在这种情况下,该函数仅在两个提供的索引之间搜索。

val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort()
println(numbers)
println(numbers.binarySearch("two"))  // 3
println(numbers.binarySearch("z")) // -5
println(numbers.binarySearch("two", 0, 2))  // -3

Comparator 二分搜索

如果列表元素不是 Comparable,则应提供一个用于二分搜索的 Comparator。 该列表必须根据此 Comparator 以升序排序。来看一个例子:

val productList = listOf(
    Product("WebStorm", 49.0),
    Product("AppCode", 99.0),
    Product("DotTrace", 129.0),
    Product("ReSharper", 149.0))
​
println(productList.binarySearch(Product("AppCode", 99.0), compareBy<Product> { it.price }.thenBy { it.name }))

这是一个不可排序Product 实例列表,以及一个定义排序的 Comparator:如果 p1 的价格小于 p2 的价格,则产品 p1 在产品 p2 之前。 因此,按照此顺序对列表进行升序排序后,使用 binarySearch() 查找指定的 Product的索引。

当列表使用与自然排序不同的顺序时(例如,对 String 元素不区分大小写的顺序),自定义 Comparator 也很方便。

val colors = listOf("Blue", "green", "ORANGE", "Red", "yellow")
println(colors.binarySearch("RED", String.CASE_INSENSITIVE_ORDER)) // 3

比较函数二分搜索

使用 比较 函数的二分搜索无需提供明确的搜索值即可查找元素。 取而代之的是,它使用一个比较函数将元素映射到 Int 值,并搜索函数返回 0 的元素。 该列表必须根据提供的函数以升序排序;换句话说,比较的返回值必须从一个列表元素增长到下一个列表元素。

data class Product(val name: String, val price: Double)
​
fun priceComparison(product: Product, price: Double) = sign(product.price - price).toInt()
​
fun main() {
    val productList = listOf(
        Product("WebStorm", 49.0),
        Product("AppCode", 99.0),
        Product("DotTrace", 129.0),
        Product("ReSharper", 149.0))
​
    println(productList.binarySearch { priceComparison(it, 99.0) })
}

Comparator 与比较函数二分搜索都可以针对列表区间执行。

List 写操作

除了集合写操作中描述的集合修改操作之外,可变列表还支持特定的写操作。 这些操作使用索引来访问元素以扩展列表修改功能。

添加 要将元素添加到列表中的特定位置,请使用 add() 或 addAll() 并提供元素插入的位置作为附加参数。 位置之后的所有元素都将向右移动。

val numbers = mutableListOf("one", "five", "six")
numbers.add(1, "two")
numbers.addAll(2, listOf("three", "four"))
println(numbers)

更新

列表还提供了在指定位置替换元素的函数——set() 及其操作符形式 []set() 不会更改其他元素的索引。

val numbers = mutableListOf("one", "five", "three")
numbers[1] =  "two"
println(numbers)

fill() 简单地将所有集合元素的值替换为指定值。

val numbers = mutableListOf(1, 2, 3, 4)
numbers.fill(3)
println(numbers)

删除

要从列表中删除指定位置的元素,请使用 removeAt() 函数,并将位置作为参数。 在元素被删除之后出现的所有元素索引将减 1。

val numbers = mutableListOf(1, 2, 3, 4, 3)    
numbers.removeAt(1)
println(numbers)

For removing the first and the last element, there are handy shortcuts removeFirst() and removeLast(). Note that on empty lists, they throw an exception. To receive null instead, use removeFirstOrNull() and removeLastOrNull()

val numbers = mutableListOf(1, 2, 3, 4, 3)    
numbers.removeFirst()
numbers.removeLast()
println(numbers)
​
val empty = mutableListOf<Int>()
// empty.removeFirst() // NoSuchElementException: List is empty.
empty.removeFirstOrNull() //null

排序

在集合排序中,描述了按特定顺序检索集合元素的操作。 对于可变列表,标准库中提供了类似的扩展函数,这些扩展函数可以执行相同的排序操作。 将此类操作应用于列表实例时,它将更改指定实例中元素的顺序。

就地排序函数的名称与应用于只读列表的函数的名称相似,但没有 ed/d 后缀:

  • sort*在所有排序函数的名称中代替 sorted*:sort()、sortDescending()、sortBy() 等等。
  • shuffle() 代替 shuffled()
  • reverse() 代替 reversed()。 asReversed() 在可变列表上调用会返回另一个可变列表,该列表是原始列表的反向视图。在该视图中的更改将反映在原始列表中。 以下示例展示了可变列表的排序函数:
    val numbers = mutableListOf("one", "two", "three", "four")
    ​
    numbers.sort()
    println("Sort into ascending: $numbers")
    numbers.sortDescending()
    println("Sort into descending: $numbers")
    ​
    numbers.sortBy { it.length }
    println("Sort into ascending by length: $numbers")
    numbers.sortByDescending { it.last() }
    println("Sort into descending by the last letter: $numbers")
    ​
    numbers.sortWith(compareBy<String> { it.length }.thenBy { it })
    println("Sort by Comparator: $numbers")
    ​
    numbers.shuffle()
    println("Shuffle: $numbers")
    ​
    numbers.reverse()
    println("Reverse: $numbers")

    Set 相关操作

    Kotlin 集合包中包含 set 常用操作的扩展函数:查找交集、并集或差集。

要将两个集合合并为一个(并集),可使用 union() 函数。也能以中缀形式使用 a union b。 注意,对于有序集合,操作数的顺序很重要:在结果集合中,左侧操作数在前。

要查找两个集合中都存在的元素(交集),请使用 intersect() 。 要查找另一个集合中不存在的集合元素(差集),请使用 subtract() 。 这两个函数也能以中缀形式调用,例如, a intersect b

val numbers = setOf("one", "two", "three")
​
println(numbers union setOf("four", "five"))
println(setOf("four", "five") union numbers)
​
println(numbers intersect setOf("two", "one"))
println(numbers subtract setOf("three", "four"))
println(numbers subtract setOf("four", "three")) // 相同的输出

注意, List 也支持 Set 操作。 但是,对 List 进行 Set 操作的结果仍然是 Set,因此将删除所有重复的元素。

Map 相关操作

在 map 中,键和值的类型都是用户定义的。 对基于键的访问启用了各种特定于 map 的处理函数,从键获取值到对键和值进行单独过滤。 在此页面上,我们提供了来自标准库的 map 处理功能的描述。

取键与值 要从 Map 中检索值,必须提供其键作为 get() 函数的参数。 还支持简写 [key] 语法。 如果找不到给定的键,则返回 null 。 还有一个函数 getValue() ,它的行为略有不同:如果在 Map 中找不到键,则抛出异常。 此外,还有两个选项可以解决键缺失的问题:

  • getOrElse() 与 list 的工作方式相同:对于不存在的键,其值由给定的 lambda 表达式返回。
  • getOrDefault() 如果找不到键,则返回指定的默认值。
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap.get("one"))
    println(numbersMap["one"])
    println(numbersMap.getOrDefault("four", 10))
    println(numbersMap["five"])               // null
    //numbersMap.getValue("six")      // exception!
    要对 map 的所有键或所有值执行操作,可以从属性 keysvalues 中相应地检索它们。 keys 是 Map 中所有键的集合, values 是 Map 中所有值的集合。
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap.keys)
    println(numbersMap.values)

    过滤

    可以使用 filter() 函数来过滤 map 或其他集合。 对 map 使用 filter() 函数时, Pair 将作为参数的谓词传递给它。 它将使用谓词同时过滤其中的键和值。
    val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
    println(filteredMap)
    还有两种用于过滤 map 的特定函数:按键或按值。 这两种方式,都有对应的函数: filterKeys() 和 filterValues() 。 两者都将返回一个新 Map ,其中包含与给定谓词相匹配的条目。 filterKeys() 的谓词仅检查元素键, filterValues() 的谓词仅检查值。
    val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredKeysMap = numbersMap.filterKeys { it.endsWith("1") }
    val filteredValuesMap = numbersMap.filterValues { it < 10 }
    ​
    println(filteredKeysMap)
    println(filteredValuesMap)

    plus 与 minus 操作

    由于需要访问元素的键,plus(+)与 minus(-)运算符对 map 的作用与其他集合不同。 plus 返回包含两个操作数元素的 Map :左侧的 Map 与右侧的 Pair 或另一个 Map 。 当右侧操作数中有左侧 Map 中已存在的键时,该条目将使用右侧的值。
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap + Pair("four", 4))
    println(numbersMap + Pair("one", 10))
    println(numbersMap + mapOf("five" to 5, "one" to 11))
    minus 将根据左侧 Map 条目创建一个新 Map ,右侧操作数带有键的条目将被剔除。 因此,右侧操作数可以是单个键或键的集合: list 、 set 等。
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap - "one")
    println(numbersMap - listOf("two", "four"))
    关于在可变 Map 中使用 plusAssign(+=)与 minusAssign(-=)运算符的详细信息,请参见 Map 写操作 。

Map 写操作

Mutable Map (可变 Map )提供特定的 Map 写操作。 这些操作使你可以使用键来访问或更改 Map 值。

Map 写操作的一些规则:

值可以更新。 反过来,键也永远不会改变:添加条目后,键是不变的。 每个键都有一个与之关联的值。也可以添加和删除整个条目。 下面是对可变 Map 中可用写操作的标准库函数的描述。

添加与更新条目

要将新的键值对添加到可变 Map ,请使用 put() 。 将新条目放入 LinkedHashMap (Map的默认实现)后,会添加该条目,以便在 Map 迭代时排在最后。 在 Map 类中,新元素的位置由其键顺序定义。

val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
println(numbersMap)

要一次添加多个条目,请使用 putAll() 。它的参数可以是 Map 或一组 PairIterableSequenceArray

val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
numbersMap.putAll(setOf("four" to 4, "five" to 5))
println(numbersMap)

如果给定键已存在于 Map 中,则 put()putAll()都将覆盖值。 因此,可以使用它们来更新 Map 条目的值。

val numbersMap = mutableMapOf("one" to 1, "two" to 2)
val previousValue = numbersMap.put("one", 11)
println("value associated with 'one', before: $previousValue, after: ${numbersMap["one"]}")
println(numbersMap)

还可以使用快速操作符将新条目添加到 Map 。 有两种方式:

  • plusAssign (+=) 操作符。
  • [] 操作符为 set() 的别名。
    val numbersMap = mutableMapOf("one" to 1, "two" to 2)
    numbersMap["three"] = 3     // 调用 numbersMap.set("three", 3)
    numbersMap += mapOf("four" to 4, "five" to 5)
    println(numbersMap)
    使用 Map 中存在的键进行操作时,将覆盖相应条目的值。

删除条目

要从可变 Map 中删除条目,请使用 remove() 函数。 调用 remove() 时,可以传递键或整个键值对。 如果同时指定键和值,则仅当键值都匹配时,才会删除此的元素。

val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
numbersMap.remove("one")
println(numbersMap)
numbersMap.remove("three", 4)            //不会删除任何条目
println(numbersMap)

还可以通过键或值从可变 Map 中删除条目。 在 Map 的 .keys.values 中调用 remove() 并提供键或值来删除条目。 在 .values 中调用时, remove() 仅删除给定值匹配到的的第一个条目。

val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3, "threeAgain" to 3)
numbersMap.keys.remove("one")
println(numbersMap)
numbersMap.values.remove(3)
println(numbersMap)

minusAssign (-=) 操作符也可用于可变 Map 。

val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
numbersMap -= "two"
println(numbersMap)
numbersMap -= "five"             //不会删除任何条目
println(numbersMap)
Target platform: JVMRunning on kotlin v. 1.4.20
预览图
评论区

索引目录