跳到主內容

【Kotlin】Collection

來源: https://kotlinlang.org/docs/collections-overview.html
cheatsheet: 網站 , xantier_kotlin_collection-extensions.pdf 

image-1722921159525.png

【集合概述】

Kotlin標準庫提供了一套全面的工具來管理集合——這些集合是問題解決中重要且經常操作的變數數量(可能為零)的項目群組。

集合是大多數編程語言中的常見概念,所以如果你熟悉例如Java或Python的集合,可以跳過這個介紹,直接進入詳細章節。

一個集合通常包含相同類型(及其子類型)的若干對象。集合中的對象稱為元素或項目。例如,一個系所的所有學生形成一個集合,可以用來計算他們的平均年齡。

以下是Kotlin中相關的集合類型:

  • List是一個有序集合,可以通過索引——反映其位置的整數——訪問元素。元素在列表中可以出現多次。列表的一個例子是電話號碼:它是一組數字,順序很重要,而且可以重複。

  • Set是一個唯一元素的集合。它反映了數學上的集合抽象:一組不重複的對象。一般來說,集合元素的順序沒有意義。例如,彩票上的號碼形成一個集合:它們是唯一的,順序並不重要。

  • Map(或字典)是一組鍵值對鍵是唯一的,每個鍵映射到一個確切的值。值可以重複。Map用於存儲對象之間的邏輯連接,例如員工的ID和他們的職位。

Kotlin允許你在操作集合時,不用考慮存儲在其中的對象的確切類型。換句話說,向字符串列表中添加字符串的方式與向整數列表或自定義類別列表中添加元素的方式相同。因此,Kotlin標準庫提供了泛型介面、類別和函數,用於創建、填充和管理任何類型的集合。

集合介面和相關函數位於kotlin.collections package中。我們來概覽一下其內容。


【構建集合】

從元素構建

創建集合最常見的方法是使用標準庫函數listOf<T>()setOf<T>()mutableListOf<T>()mutableSetOf<T>()。如果你提供一個以逗號分隔的集合元素列表作為參數,編譯器會自動檢測元素類型。在創建空集合時,需要明確指定類型。

val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()

同樣的功能也適用於map,使用函數mapOf()mutableMapOf()。map的鍵和值是以Pair對象傳遞的(通常通過to中綴函數創建)。

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

需要注意的是,to表示法會創建一個短暫存在的Pair對象,所以建議僅在性能不是關鍵時使用它。為了避免過多的記憶體使用,可以使用其他方式。例如,可以創建一個可變map並使用寫操作來填充它。apply()函數可以幫助保持初始化的流暢性。

val numbersMap = mutableMapOf<String, String>().apply { 
    this["one"] = "1"
    this["two"] = "2" 
}

使用集合構建函數創建

另一種創建集合的方法是調用構建函數——buildList()buildSet()buildMap()。它們創建一個新的、可變的相應類型的集合,使用寫操作填充它,然後返回包含相同元素的只讀集合:

val map = buildMap { 
    put("a", 1)
    put("b", 0)
    put("c", 4)
}

println(map) // {a=1, b=0, c=4}

空集合

也有一些函數可以創建沒有任何元素的集合:emptyList()emptySet()emptyMap()。在創建空集合時,應該明確指定集合將持有的元素類型。

val empty = emptyList<String>()

List的初始化函數

對於list,有一個類似構造函數的函數,它接受列表大小和基於索引定義元素值的初始化函數。

val doubled = List(3) { it * 2 }
println(doubled)
// [0, 2, 4]

具體類型構造函數

要創建具體類型的集合,例如ArrayListLinkedList,可以使用這些類型的可用構造函數。對於SetMap的實現也有類似的構造函數。

val linkedList = LinkedList<String>(listOf("one", "two", "three"))
val presizedSet = HashSet<Int>(32)

複製

要創建一個包含與現有集合相同元素的集合,可以使用複製函數。標準庫的集合複製函數會創建淺層複製集合,引用相同的元素。因此,對集合元素所做的更改會反映在所有其副本中。

集合複製函數,如toList()toMutableList()toSet()等,會在特定時刻創建集合的快照。其結果是一個包含相同元素的新集合。如果從原集合中添加或移除元素,這不會影響副本。副本可以獨立於源進行更改。

val alice = Person("Alice")
val sourceList = mutableListOf(alice, Person("Bob"))
val copyList = sourceList.toList()
sourceList.add(Person("Charles"))
alice.name = "Alicia"
println("First item's name is: ${sourceList[0].name} in source and ${copyList[0].name} in copy")
println("List size is: ${sourceList.size} in source and ${copyList.size} in copy")

// First item's name is: Alicia in source and Alicia in copy
// List size is: 3 in source and 2 in copy

這些函數也可以用來將集合轉換為其他類型,例如從列表構建集合,反之亦然。

val sourceList = mutableListOf(1, 2, 3)    
val copySet = sourceList.toMutableSet()
copySet.add(3)
copySet.add(4)    
println(copySet)
 // [1, 2, 3, 4]

另外,可以創建對同一集合實例的新引用。當你使用現有集合初始化一個集合變量時,就會創建新引用。因此,當通過某個引用修改集合實例時,這些更改會反映在所有引用中。

val sourceList = mutableListOf(1, 2, 3)
val referenceList = sourceList
referenceList.add(4)
println("Source size: ${sourceList.size}") // Source size: 4

集合初始化可以用來限制可變性。例如,如果你創建一個List引用到一個MutableList,編譯器會在你試圖通過此引用修改集合時產生錯誤。

val sourceList = mutableListOf(1, 2, 3)
val referenceList: List<Int> = sourceList
//referenceList.add(4)            //compilation error
sourceList.add(4)
println(referenceList) // shows the current state of sourceList

調用其他集合上的函數

集合可以作為對其他集合進行各種操作的結果來創建。例如,過濾列表會創建一個包含匹配過濾條件的元素的新列表:

val numbers = listOf("one", "two", "three", "four")  
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3) 
// [three, four]
 

map 會根據轉換結果產生一個列表:

val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })
// [3, 6, 9]
// [0, 2, 6]

associate 產生maps:

val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })

 // {one=3, two=3, three=5, four=4}

有關Kotlin中集合操作的更多信息,請參見集合操作概述。


【迭代器(Iterator)】

為了遍歷集合元素,Kotlin標準庫支持常用的迭代器機制——這些對象可以順序地訪問元素而不暴露集合的底層結構。當你需要一個一個地處理集合中的所有元素時,迭代器非常有用,例如,打印值或進行類似的更新。

迭代器可以通過調用iterator()函數從Iterable<T>介面的繼承者(包括Set和List)獲得。

一旦獲得迭代器,它會指向集合的第一個元素;調用next()函數返回該元素並將迭代器位置移動到下一個元素(如果存在的話)。

一旦迭代器遍歷了最後一個元素,它將不能再用於檢索元素;也不能將其重置到任何先前的位置。要再次遍歷集合,需要創建一個新的迭代器。

val numbers = listOf("one", "two", "three", "four")
val numbersIterator = numbers.iterator()
while (numbersIterator.hasNext()) {
    println(numbersIterator.next())
    // one
    // two
    // three
    // four
}

另一種遍歷Iterable集合的方法是眾所周知的for循環。當在集合上使用for時,你會隱式地獲得迭代器。因此,下面的代碼等同於上面的例子:

val numbers = listOf("one", "two", "three", "four")
for (item in numbers) {
    println(item)
    // one
    // two
    // three
    // four
}

最後,有一個有用的forEach()函數,讓你可以自動迭代集合並為每個元素執行給定的代碼。因此,相同的例子看起來如下:

val numbers = listOf("one", "two", "three", "four")
numbers.forEach {
    println(it)
    // one
    // two
    // three
    // four
}

列表迭代器

對於列表,有一個特殊的迭代器實現:ListIterator。它支持雙向迭代列表:向前向後

向後迭代通過hasPrevious()previous()函數實現。此外,ListIterator還提供有關元素索引的信息,通過nextIndex()previousIndex()函數。

val numbers = listOf("one", "two", "three", "four")
val listIterator = numbers.listIterator()
while (listIterator.hasNext()) listIterator.next()
println("Iterating backwards:")
// Iterating backwards:
while (listIterator.hasPrevious()) {
    print("Index: ${listIterator.previousIndex()}")
    println(", value: ${listIterator.previous()}")
    // Index: 3, value: four
    // Index: 2, value: three
    // Index: 1, value: two
    // Index: 0, value: one
}

能夠雙向迭代意味著ListIterator在到達最後一個元素後仍然可以使用。

可變迭代器

對於迭代可變集合,有MutableIterator,它擴展了Iterator並增加了元素移除函數remove()。因此,你可以在迭代集合時移除元素。

val numbers = mutableListOf("one", "two", "three", "four") 
val mutableIterator = numbers.iterator()

mutableIterator.next()
mutableIterator.remove()    
println("After removal: $numbers")
// After removal: [two, three, four]

除了移除元素外,MutableListIterator還可以在迭代列表時插入和替換元素,通過使用add()set()函數。

val numbers = mutableListOf("one", "four", "four") 
val mutableListIterator = numbers.listIterator()

mutableListIterator.next()
mutableListIterator.add("two")
println(numbers)
// [one, two, four, four]
mutableListIterator.next()
mutableListIterator.set("three")   
println(numbers)
// [one, two, three, four]

【範圍(Ranges)與級數(Progessions)】

Kotlin允許你使用kotlin.ranges包中的.rangeTo().rangeUntil()函數輕鬆創建值的範圍。

範圍

要創建:

  • 閉合範圍,使用..運算符調用.rangeTo()函數。
  • 開放範圍,使用..<運算符調用.rangeUntil()函數。

例如:

// 閉合範圍
println(4 in 1..4)  // true

// 開放範圍
println(4 in 1..<4)  // false

範圍特別適用於在for循環中進行迭代:

for (i in 1..4) print(i)
// 1234

要反向迭代數字,可以使用downTo函數代替..

for (i in 4 downTo 1) print(i)
// 4321

也可以使用step函數以任意步長(不一定是1)迭代數字:

for (i in 0..8 step 2) print(i)
println()
// 02468

for (i in 0..<8 step 2) print(i)
println()
// 0246

for (i in 8 downTo 0 step 2) print(i)
// 86420

級數

整數類型(如Int、Long和Char)的範圍可以視為算術級數。在Kotlin中,這些級數由特定類型定義:IntProgressionLongProgressionCharProgression

級數有三個基本屬性:第一個元素最後一個元素非零步長。第一個元素是first,隨後的元素是前一個元素加上步長。帶有正步長的級數迭代等同於Java/JavaScript中的索引for循環。

for (int i = first; i <= last; i += step) {
  // ...
}

當你通過迭代範圍隱式地創建級數時,這個級數的第一個和最後一個元素是範圍的端點,步長為1。

for (i in 1..10) print(i)
// 12345678910

要定義自訂的級數步長,可以在範圍上使用step函數。

for (i in 1..8 step 2) print(i)
// 1357

級數的最後一個元素計算方法如下:

  • 對於正步長:不大於結束值的最大值,使得 (last - first) % step == 0
  • 對於負步長:不小於結束值的最小值,使得 (last - first) % step == 0

因此,最後一個元素不一定是指定的結束值。

for (i in 1..9 step 3) print(i) // 最後一個元素是7
// 147

級數實現了Iterable<N>,其中N分別是IntLongChar,所以你可以在各種集合函數中使用它們,如mapfilter等。

println((1..10).filter { it % 2 == 0 })
// [2, 4, 6, 8, 10]

【序列(Sequence)】

除了集合,Kotlin標準庫還包含另一種類型——序列(Sequence)。與集合不同,序列不包含元素,而是在迭代時生成它們。序列提供與Iterable相同的函數,但實現了另一種多步集合處理的方法。

當對Iterable進行多步處理時,這些步驟是急切執行的:每個處理步驟完成並返回其結果——一個中間集合。下一步在這個集合上執行。相反,序列的多步處理在可能的情況下是懶惰執行的:實際計算僅在請求整個處理鏈的結果時發生。

操作執行的順序也不同:序列對每個元素依次執行所有處理步驟。相反,Iterable完成整個集合的每一步,然後進入下一步。

因此,序列使你可以避免構建中間步驟的結果,從而提高整個集合處理鏈的性能。然而,序列的懶惰性質增加了一些開銷,這在處理較小的集合或進行簡單計算時可能是顯著的。因此,你應該同時考慮序列和Iterable,並決定哪個更適合你的情況。

創建

從元素創建

要創建序列,請調用sequenceOf()函數並列出其參數中的元素。

val numbersSequence = sequenceOf("four", "three", "two", "one")

Iterable創建

如果你已經有一個Iterable對象(例如ListSet),可以通過調用asSequence()從中創建序列。

val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()

從函數創建

另一種創建序列的方法是使用計算其元素的函數來構建它。要基於函數創建序列,請調用generateSequence()並將此函數作為參數。可以選擇性地將第一個元素指定為明確的值或函數調用的結果。當提供的函數返回null時,序列生成停止。因此,下面示例中的序列是無限的

val oddNumbers = generateSequence(1) { it + 2 } // `it` 是前一個元素
println(oddNumbers.take(5).toList())
// println(oddNumbers.count())     // 錯誤:序列是無限的

要使用generateSequence()創建有限序列,請提供在最後一個元素之後返回null的函數。

val oddNumbersLessThan10 = generateSequence(1) { if (it < 8) it + 2 else null }
println(oddNumbersLessThan10.count())

從塊創建(from chunks)

最後,有一個函數允許你逐個或按任意大小的塊生成序列元素——sequence()函數。該函數接受包含yield()yieldAll()函數調用的lambda表達式。它們將元素返回給序列消費者,並掛起sequence()的執行,直到消費者請求下一個元素。yield()以單個元素作為參數;yieldAll()可以接受Iterable對象、迭代器或另一個序列。yieldAll()的序列參數可以是無限的。然而,這樣的調用必須是最後一個:所有後續調用將永遠不會執行。

val oddNumbers = sequence {
    yield(1)
    yieldAll(listOf(3, 5))
    yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList()) 
// [1, 3, 5, 7, 9]

序列操作

序列操作根據其狀態要求分為以下幾類:

  • 無狀態操作不需要狀態並獨立處理每個元素,例如map()filter()。無狀態操作也可以需要少量常數狀態來處理元素,例如take()drop()
  • 有狀態操作:需要大量狀態,通常與序列中元素的數量成正比。

如果序列操作返回另一個序列,該序列是懶惰生成的,則稱為中間操作。否則,該操作是終端操作。例如,終端操作包括toList()sum()。序列元素只能通過終端操作檢索。

序列可以多次迭代;但是某些序列實現可能會限制自己只能迭代一次。在它們的文檔中會特別提到這一點。

序列處理示例

讓我們通過一個例子來看看Iterable和序列之間的區別。

Iterable

假設你有一個單詞列表。下面的代碼過濾出長度超過三個字符的單詞,並打印出前四個這樣的單詞的長度。

val words = "The quick brown fox jumps over the lazy dog".split(" ")
val lengthsList = words.filter { println("filter: $it"); it.length > 3 }
    .map { println("length: ${it.length}"); it.length }
    .take(4)

println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)

/*      
filter: The
filter: quick
filter: brown
filter: fox
filter: jumps
filter: over
filter: the
filter: lazy
filter: dog
length: 5
length: 5
length: 5
length: 4
length: 4
Lengths of first 4 words longer than 3 chars:
[5, 5, 5, 4]
*/

運行此代碼時,你會看到filter()map()函數按代碼中出現的順序執行。首先,你會看到所有元素的filter:,然後是過濾後的元素的length:,最後是最後兩行的輸出。

這就是列表處理的方式:

image-1722308042976.png

列表處理

序列

現在用序列寫相同的代碼:

val words = "The quick brown fox jumps over the lazy dog".split(" ")
// 將List轉換為Sequence
val wordsSequence = words.asSequence()

val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 }
    .map { println("length: ${it.length}"); it.length }
    .take(4)

println("Lengths of first 4 words longer than 3 chars:")
// 終端操作:將結果作為List獲取
println(lengthsSequence.toList())
/**
Lengths of first 4 words longer than 3 chars:
filter: The
filter: quick
length: 5
filter: brown
length: 5
filter: fox
filter: jumps
length: 5
filter: over
length: 4
[5, 5, 5, 4]
*/

此代碼的輸出顯示,filter()map()函數僅在構建結果列表時調用。因此,你首先看到文本行"Lengths of..",然後序列處理開始。注意,對於過濾後的元素,map在過濾下一個元素之前執行。當結果大小達到4時,處理停止,因為這是take(4)可以返回的最大大小。

序列處理如下:

image-1722308057841.png

序列處理

在此示例中,序列處理用了18步,而使用列表的處理用了23步。

希望這些範例有助於你在開發Kotlin程式時瞭解序列與集合的使用差異及其優劣勢,特別是在處理多步集合操作時。


【集合操作概述】

Kotlin標準庫提供了多種函數,用於對集合進行操作。這些操作包括簡單的操作(如獲取或添加元素)以及更複雜的操作(如搜索、排序、過濾、轉換等)。

擴展函數與成員函數

集合操作在標準庫中以兩種方式聲明:集合介面的成員函數和擴展函數。

成員函數定義了集合類型所必需的操作。例如,Collection包含檢查其是否為空的函數isEmpty()List包含用於索引訪問元素的函數get()等。

當你創建自己的集合介面實現時,必須實現其成員函數。為了使新實現的創建更容易,可以使用標準庫中的集合介面的骨架實現:AbstractCollectionAbstractListAbstractSetAbstractMap及其可變對應物。

其他集合操作則聲明為擴展函數。這些包括過濾、轉換、排序和其他集合處理函數。

常用操作

常用操作可用於只讀和可變集合。常用操作分為以下幾組:

  • 轉換 (map, zip, associate, flatten)
  • 過濾 (filter)
  • 加號和減號操作符 (+, -)
  • 分組 (groupBy)
  • 檢索集合部分 (slice, tack drop, chunked, window)
  • 檢索單個元素 (get, find, first, last, random)
  • 排序 (sorted)
  • 聚合操作 (minOrNull, maxOrNull, average, sum, count, fold, reduce)

這些頁面上描述的操作返回其結果不影響原始集合。例如,過濾操作生成一個包含所有匹配過濾條件的元素的新集合。這些操作的結果應該存儲在變量中,或以其他方式使用,例如,傳遞給其他函數。

val numbers = listOf("one", "two", "three", "four")
numbers.filter { it.length > 3 }  // numbers沒有任何變化,結果丟失
println("numbers are still $numbers")
val longerThan3 = numbers.filter { it.length > 3 } // 結果存儲在longerThan3中
println("numbers longer than 3 chars are $longerThan3")

對於某些集合操作,可以指定目標對象。目標對象是可變集合,函數將其結果項附加到該集合中,而不是返回新的對象。對於具有目標的操作,有帶有To後綴的單獨函數名稱,例如filterTo()代替filter()associateTo()代替associate()。這些函數將目標集合作為附加參數。

val numbers = listOf("one", "two", "three", "four")
val filterResults = mutableListOf<String>()  // 目標對象
numbers.filterTo(filterResults) { it.length > 3 }
println(filterResults) // [three, four]
numbers.filterIndexedTo(filterResults) { index, _ -> index == 0 }
println(filterResults) // [three, four, one] 包含兩次操作的結果

為了方便起見,這些函數返回目標集合,因此你可以在函數調用的相應參數中直接創建它:

// 將數字過濾到新的Hashset,從而消除結果中的重複項
val numbers = listOf("one", "two", "three", "four")
val result = numbers.mapTo(HashSet()) { it.length }
println("distinct item lengths are $result")
// distinct item lengths are [3, 4, 5] 

或是已經初始化後的資料附加在後面    

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

  val tmpMap = mutableListOf<String>("aa")
  val tmpMap2 = mutableListOf<String>("bb")
  println(numbers.mapTo(tmpMap){it})
  // [aa, one, two, three, four, five]
  println(numbers.filterTo(tmpMap2){it.length > 3})
  // [bb, three, four, five]

寫操作

對於可變集合,還有改變集合狀態的寫操作。這些操作包括添加、刪除和更新元素。寫操作列在寫操作以及List特定操作和Map特定操作的對應部分中。

對於某些操作,有成對的函數執行相同的操作:一個在原地應用操作,另一個返回結果作為單獨的集合。例如,sort()對可變集合進行原地排序,因此其狀態改變;sorted()創建一個包含相同元素的新集合,按排序順序排列。

val numbers = mutableListOf("one", "two", "three", "four")
val sortedNumbers = numbers.sorted()
println(sortedNumbers)				// [four, one, three, two]
println(numbers == sortedNumbers)   // false
numbers.sort()
println(numbers)					// [four, one, three, two]
println(numbers == sortedNumbers)   // true

【集合轉換操作】

Kotlin標準庫提供了一組用於集合轉換的擴展函數。這些函數根據提供的轉換規則從現有集合構建新集合。以下是可用的集合轉換函數概述。

映射 (Map)

  • map
  • mapIndexed
  • mapNotNull
  • mapIndexedNotNull
  • mapKeys
  • mapValues

映射轉換通過對另一個集合的元素應用函數來創建集合。基本的映射函數是map()。它將給定的lambda函數應用於每個元素,並返回lambda結果的列表。結果的順序原始元素的順序相同。要應用同時使用元素索引作為參數的轉換,請使用mapIndexed()

val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
// [3, 6, 9]
println(numbers.mapIndexed { idx, value -> value * idx })
// [0, 2, 6]

如果轉換在某些元素上產生null,可以通過調用mapNotNull()代替map(),或mapIndexedNotNull()代替mapIndexed()來過濾結果集合中的null。

val numbers = setOf(1, 2, 3)
println(numbers.mapNotNull { if (it == 2) null else it * 3 })
// [3, 9]
println(numbers.mapIndexedNotNull { idx, value -> if (idx == 0) null else value * idx })
// [2, 6]

在轉換Map時,你有兩個選擇:變換鍵而保持值不變,反之亦然。要對鍵應用給定的轉換,使用mapKeys();反過來,mapValues()轉換值。這兩個函數都使用將map條目作為參數的轉換,因此你可以同時操作其鍵和值。

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
println(numbersMap.mapKeys { it.key.uppercase() })
// {KEY1=1, KEY2=2, KEY3=3, KEY11=11}
println(numbersMap.mapValues { it.value + it.key.length })
// {key1=5, key2=6, key3=7, key11=16}

Zip

zip轉換是從兩個集合中相同位置的元素構建對。在Kotlin標準庫中,這是通過zip()擴展函數完成的。

當在一個集合或數組上調用並將另一個集合(或數組)作為參數時,zip()返回一個Pair對象的列表。接收集合的元素是這些對中的第一個元素。

如果集合大小不同,zip()的結果是較小的大小;較大集合的最後元素不包括在結果中。

zip()也可以以中綴形式調用,即a zip b

val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")
println(colors zip animals)
// [(red, fox), (brown, bear), (grey, wolf)]
  
val twoAnimals = listOf("fox", "bear")
println(colors.zip(twoAnimals))
// [(red, fox), (brown, bear)]

你也可以用一個帶有兩個參數的轉換函數調用zip():接收元素和參數元素。在這種情況下,結果列表包含對接收元素和參數元素的對調用轉換函數的返回值。

val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")

println(colors.zip(animals) { color, animal -> "The ${animal.replaceFirstChar { it.uppercase() }} is $color"})
// [The Fox is red, The Bear is brown, The Wolf is grey]

當你有一個Pair列表時,可以進行反向轉換——解壓縮,從這些對中構建兩個列表:

第一個列表包含原始列表中每個Pair的第一個元素。

第二個列表包含第二個元素。

要解壓縮一個Pair列表,調用unzip()

    
val numberPairs = listOf("one" to 1, "two" to 2, "three" to 3, "four" to 4)
println(numberPairs.unzip()) 			// 返回 pair ([one, two, three, four], [1, 2, 3, 4])
println(numberPairs.unzip().toList()) 	// 返回 List [[one, two, three, four], [1, 2, 3, 4]]

關聯 (Associate) (list to map)

  • associateWith
  • associateWithTo
  • associateBy
  • associate

關聯轉換允許從集合元素及其相關的某些值構建map。在不同的關聯類型中,元素可以是關聯map中的鍵或值。

基本的關聯函數associateWith()創建一個map,其中原始集合的元素是鍵,值是由給定的轉換函數生成的。如果兩個元素相等,只有最後一個會保留在map中。

val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })

// {one=3, two=3, three=5, four=4}
val fruits = listOf("apple", "banana", "cherry")

val lengthMap = mutableMapOf<String, Int>()
fruits.associateWithTo(lengthMap) { it.length }

println(lengthMap)  // {apple=5, banana=6, cherry=6}

要構建map,其集合元素作為值,有associateBy()函數。它接收一個函數,該函數根據元素的值返回鍵。如果兩個元素的鍵相等,只有最後一個會保留在map中。

associateBy()也可以與值轉換函數一起調用。

val numbers = listOf("one", "two", "three", "four")

println(numbers.associateBy { it.first().uppercaseChar() }
// {O=one, T=three, F=four}
println(numbers.associateBy({ it.first() }, { it.length }))
// {O=one, T=three, F=four}    
println(numbers.associateBy(keySelector = { it.first().uppercaseChar() }, valueTransform = { it.length }))
// {O=3, T=5, F=4}
        
        

另一種方法是使用associate()構建map,其中鍵和值都從集合元素中生成。它接收一個lambda函數,該函數返回一個Pair:對應的map條目的鍵和值。

需要注意的是,associate()會產生短暫存在的Pair對象,這可能會影響性能。因此,當性能不是關鍵或比其他選項更可取時,應使用associate()

一個示例是當鍵和值是一起從元素生成時。

val names = listOf("Alice Adams", "Brian Brown", "Clara Campbell")
println(names.associate { name -> parseFullName(name).let { it.lastName to it.firstName } })

// {Adams=Alice, Brown=Brian, Campbell=Clara}

在這裡,我們首先對元素調用轉換函數,然後從該函數的結果屬性構建一個對。

展平 (Flatten)

  • flatten
  • flatMap

如果你操作嵌套集合,你可能會發現標準庫提供的平展訪問嵌套集合元素的函數很有用。

第一個函數是flatten()。你可以在集合的集合上調用它,例如,一個ListSet。該函數返回一個包含所有嵌套集合元素的單個列表。

val numberSets = listOf(setOf(1, 2, 3), setOf(4, 5, 6), setOf(1, 2))
println(numberSets)
// [[1, 2, 3], [4, 5, 6], [1, 2]]
println(numberSets.flatten())
// [1, 2, 3, 4, 5, 6, 1, 2]

另一個函數flatMap()提供了一種靈活的方式來處理嵌套集合。它接收一個函數,該函數將集合元素映射到另一個集合。結果,flatMap()返回其對所有元素的返回值的單個列表。因此,flatMap()的行為類似於隨後調用map()(以集合作為映射結果)和flatten()

// 就是先map 再flatten 
val list = listOf(1, 2, 3)
val result = list.flatMap { listOf(it, it * 2) }
// [1, 2, 2, 4, 3, 6]
data class Person(var name:String,var age:Int)

fun main() {
    val numberSets = listOf( 
        listOf(Person("A",12), Person("B",13)), 
        listOf(Person("C",11), Person("D",14), Person("B",13))
    )
    println(numberSets)
    /*
    [
    	[Person(name=A, age=12), Person(name=B, age=13)], 
    	[Person(name=C, age=11), Person(name=D, age=14), Person(name=B, age=13)]
    ]
    */
    println(numberSets.flatMap{it})
    // [Person(name=A, age=12), Person(name=B, age=13), Person(name=C, age=11), Person(name=D, age=14), Person(name=B, age=13)]
}

字符串表示 (String representation)

  • joinToString
  • joinTo

如果你需要以可讀格式檢索集合內容,請使用將集合轉換為字符串的函數:joinToString()joinTo()

joinToString()根據提供的參數構建單個字符串。joinTo()做同樣的事,但將結果附加到給定的Appendable對象。

當使用默認參數調用時,這些函數返回的結果類似於對集合調用toString():元素的字符串表示形式以逗號和空格分隔的字符串。

val numbers = listOf("one", "two", "three", "four")

println(numbers)   // [one, two, three, four]      
println(numbers.joinToString()) // one, two, three, four

val listString = StringBuffer("The list of numbers: ")
numbers.joinTo(listString)
println(listString)
// The list of numbers: one, two, three, four

要構建自定義字符串表示,可以在函數參數中指定其參數:分隔符、前綴和後綴。結果字符串將以前綴開始,以後綴結束。分隔符將在每個元素之後出現,除了最後一個元素。

val numbers = listOf("one", "two", "three", "four")    
println(numbers.joinToString(separator = " | ", prefix = "start: ", postfix = ": end"))
// start: one | two | three | four: end

對於較大的集合,你可能希望指定限制——將包含在結果中的元素數量。如果集合大小超過限制,所有其他元素將被替換為單個截斷參數值。

val numbers = (1..100).toList()
println(numbers.joinToString(limit = 10, truncated = "<...>"))
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, <...>

最後,要自定義元素本身的表示,請提供 transform 函數。

val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString { "Element: ${it.uppercase()}"})
// Element: ONE, Element: TWO, Element: THREE, Element: FOUR

【過濾集合】

過濾是集合處理中最常見的任務之一。在Kotlin中,過濾條件由謂詞定義——這些lambda函數接收一個集合元素並返回一個布爾值:true表示給定元素符合謂詞,false表示相反。

標準庫包含一組擴展函數,允許你在一次調用中過濾集合。這些函數不會更改原始集合,因此它們可用於可變和只讀集合。要操作過濾結果,應將其賦值給變量或在過濾後鏈接函數。

根據條件過濾

  • filter
  • filterIndexed
  • fiterNot
  • filterIsInstance
  • filterNotNull

基本的過濾函數是filter()。當用謂詞調用時,filter()返回符合條件的集合元素。對於List和Set,返回結果是List,對於Map,返回結果也是Map

val numbers = listOf("one", "two", "three", "four")  
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3) // [three, four]

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) // {key11=11}

filter()中的謂詞只能檢查元素的值。如果你想在過濾中使用元素的位置,請使用filterIndexed()。它接收一個帶有兩個參數的謂詞:索引和元素的值。

要根據否定條件過濾集合,使用filterNot()。它返回一個對謂詞結果為false的元素列表。

val numbers = listOf("one", "two", "three", "four")

val filteredIdx = numbers.filterIndexed { index, s -> (index != 0) && (s.length < 5) }
val filteredNot = numbers.filterNot { it.length <= 3 }

println(filteredIdx) // [two, four]
println(filteredNot) // [three, four]

還有一些函數可以通過過濾特定類型的元素來縮小元素類型:

  • filterIsInstance()返回給定類型的集合元素。對List<Any>調用filterIsInstance<T>()返回List<T>,從而允許你對其項目調用T類型的函數。
val numbers = listOf(null, 1, "two", 3.0, "four")
println("All String elements in upper case:")
numbers.filterIsInstance<String>().forEach {
    println(it.uppercase())
}

/*
All String elements in upper case:
TWO
FOUR
*/
  • filterNotNull()返回所有非空元素。對List<T?>調用filterNotNull()返回List<T: Any>,從而允許你將元素作為非空對象處理。
val numbers = listOf(null, "one", "two", null)
numbers.filterNotNull().forEach {
    println(it.length)   // length is unavailable for nullable Strings
}

// 3
// 3

分區 (Partition)

另一個過濾函數——partition()——根據條件過濾集合,並將不符合條件的元素保存在單獨的列表中。因此,你會得到一對列表作為返回值:第一個列表包含符合條件的元素,第二個列表包含原始集合中的其他所有元素。

val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 }

println(match)
// [three, four]
println(rest)
// [one, two]

 

測試謂詞 (Test predicates)

最後,有一些函數只是將測試集合元素:

  • any():如果至少有一個元素符合判斷式,則返回true
  • none():如果沒有元素符合判斷式,則返回true
  • all():如果所有元素都符合判斷式,則返回true。注意,對空集合而言,all()返回true
val numbers = listOf("one", "two", "three", "four")

println(numbers.any { it.endsWith("e") })	// true
println(numbers.none { it.endsWith("a") })	// true
println(numbers.all { it.endsWith("e") })	// false

println(emptyList<Int>().all { it > 5 })   // true

any()none()也可以不帶判斷式:在這種情況下,它們只是檢查集合是否為空any()返回true如果有元素,否則返回falsenone()做相反的事。

val numbers = listOf("one", "two", "three", "four")
val empty = emptyList<String>()

println(numbers.any())	// true
println(empty.any())	// false

println(numbers.none())	// false
println(empty.none())	// true

【加號和減號操作符】

在Kotlin中,加號(+)和減號(-)操作符是為集合定義的。它們將集合作為第一個操作數;第二個操作數可以是元素或另一個集合。返回值是一個新的只讀集合:

  • plus的結果包含原始集合中的元素以及第二個操作數中的元素。
  • minus的結果包含原始集合中的元素,但去除了第二個操作數中的元素。如果第二個操作數是元素,minus會移除它的首次出現;如果是集合,則移除其所有元素的出現。
val numbers = listOf("one", "two", "three", "four")

val plusList = numbers + "five"
val minusList = numbers - listOf("three", "four")
println(plusList)  // [one, two, three, four, five]
println(minusList) // [one, two]

 


【分組】

Kotlin標準庫提供了一組用於對集合元素進行分組的擴展函數。基本的分組函數groupBy()接受一個lambda函數並返回一個Map。在這個Map中,每個鍵是lambda結果,對應的值是返回該結果的元素列表。此函數可以用來,例如,按字符串的首字母對字符串列表進行分組。

你還可以使用第二個lambda參數來調用groupBy(),這是一個值轉換函數。在使用兩個lambda的groupBy()的結果Map中,keySelector函數生成的鍵被映射到值轉換函數的結果,而不是原始元素。

以下示例展示了如何使用groupBy()函數按字符串的首字母對字符串進行分組,使用for運算符迭代結果Map中的組,然後使用keySelector函數將值轉換為大寫:

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

// 使用 groupBy() 按首字母對字符串進行分組
val groupedByFirstLetter = numbers.groupBy { it.first().uppercase() }
println(groupedByFirstLetter)
// {O=[one], T=[two, three], F=[four, five]}

// 迭代每個組並打印鍵及其相關的值
for ((key, value) in groupedByFirstLetter) {
    println("Key: $key, Values: $value")
}
// Key: O, Values: [one]
// Key: T, Values: [two, three]
// Key: F, Values: [four, five]

// 按首字母對字符串進行分組並將值轉換為大寫
val groupedAndTransformed = numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.uppercase() })
println(groupedAndTransformed)
// {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}

如果你想對元素進行分組,然後一次性對所有組應用操作,請使用groupingBy()函數。它返回一個Grouping類型的實例。Grouping實例允許你以懶惰的方式對所有組應用操作:這些組實際上是在操作執行之前構建的。

具體而言,Grouping支持以下操作:

  • eachCount() 計算每個組中的元素數量。
  • fold() 和 reduce() 對每個組作為單獨的集合執行fold和reduce操作,並返回結果。
  • aggregate() 將給定操作依次應用於每個組中的所有元素,並返回結果。這是對Grouping執行任何操作的通用方法。當foldreduce不夠時,使用它來實現自定義操作。

你可以在結果Map上使用for運算符來迭代由groupingBy()函數創建的組。這允許你訪問每個鍵和與該鍵相關的元素計數。

以下示例展示了如何使用groupingBy()函數按字符串的首字母對字符串進行分組,計算每個組中的元素數量,然後迭代每個組以打印鍵和元素的數量:

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

// 使用 groupingBy() 按首字母對字符串進行分組並計算每個組中的元素數量
val grouped = numbers.groupingBy { it.first() }.eachCount()

// 迭代每個組並打印鍵及其相關的值
for ((key, count) in grouped) {
    println("Key: $key, Count: $count")
    // Key: o, Count: 1
    // Key: t, Count: 2
    // Key: f, Count: 2
}

這樣,你可以根據特定的條件對集合元素進行分組,並對分組結果進行進一步的操作。

// 使用then串接多個排序
private fun sortLiveShows(shows: List<ShowGeneric>): List<ShowGeneric> {
        // 銷售版位為直播順序非99的檔次
        val (weightedShows, unweightedShows) = shows.partition { it.weight != 99 }

        // 直播順序99的檔次再分成開播時間在十分鐘之內跟之外的兩組
        val tenMinutesBefore = Date.from(Instant.now().minus(10, ChronoUnit.MINUTES))
        val (newUnweightedShows, oldUnweightedShows) = unweightedShows.partition {
            it.startAt?.after(tenMinutesBefore) == true
        }

        // 排序
        // 銷售版位檔次排序:按照直播順序排序
        val sortedWeightedShows = weightedShows.sortedBy { it.weight }
        // 開播十分鐘之內檔次排序:先按照開播時間再按照首次送審時間
        val sortedNewUnweightedShows = newUnweightedShows.sortedWith(
            compareByDescending<ShowGeneric> { it.startAt }
                .then(compareBy { it.getAuditStartAt() })
        )
        /* 也可以用theyBy
        val sortedNewUnweightedShows = newUnweightedShows.sortedWith(
            compareByDescending<ShowGeneric> { it.startAt }
                .thenBy { it.getAuditStartAt() }
        )
        */
         
        // 開播十分中之外檔次排序:random
        val sortedOldUnWeightedShows = oldUnweightedShows.shuffled()

        // 銷售版位 + 開播十分鐘之內 + 開播十分中之外
        return sortedWeightedShows + sortedNewUnweightedShows + sortedOldUnWeightedShows
    }

【檢索集合部分】

Kotlin標準庫包含一組擴展函數,用於檢索集合的部分內容。這些函數提供多種方式來選擇結果集合的元素:明確列出它們的位置、指定結果大小等。

切片 (Slice)

slice() 返回具有給定索引的集合元素列表。索引可以作為範圍或整數值的集合傳遞。

val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.slice(1..3))               // [two, three, four]
println(numbers.slice(0..4 step 2))        // [one, three, five]
println(numbers.slice(setOf(3, 5, 0)))     // [four, six, one]

取出和丟棄 (Take and drop)

要從第一個元素開始取出指定數量的元素,使用take()函數。要取出最後的元素,使用takeLast()。當傳遞的數量大於集合大小時,這兩個函數都返回整個集合。

要取出除了給定數量的第一個或最後一個元素之外的所有元素,分別調用drop()dropLast()函數。

val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.take(3))                   // [one, two, three]
println(numbers.takeLast(3))               // [four, five, six]
println(numbers.drop(1))                   // [two, three, four, five, six]
println(numbers.dropLast(5))               // [one]

你還可以使用謂詞來定義取出或丟棄的元素數量。有四個與上面描述的函數類似的函數:

  • takeWhile() 是帶有謂詞(predicates)的take():它取出直到不匹配謂詞的第一個元素為止(不包括該元素)的元素。如果第一個集合元素不匹配謂詞,結果為空。
  • takeLastWhile() 類似於takeLast():它從集合末尾取出匹配謂詞的元素範圍。範圍的第一個元素是不匹配謂詞的最後一個元素的下一個元素。如果最後的集合元素不匹配謂詞,結果為空。
  • dropWhile() 與帶有相同謂詞的takeWhile()相反:它返回從不匹配謂詞的第一個元素到結尾的元素。
  • dropLastWhile() 與帶有相同謂詞的takeLastWhile()相反:它返回從開始到不匹配謂詞的最後一個元素的元素。
val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.takeWhile { !it.startsWith('f') })     // [one, two, three, four]
println(numbers.takeLastWhile { it != "three" })       // [four, five, six]
println(numbers.dropWhile { it.length == 3 })          // [four, five, six]
println(numbers.dropLastWhile { it.contains('i') })    // [one, two, three]

分塊 (Chunked)

要將集合拆分為給定大小的部分,使用chunked()函數。chunked()接受一個參數——塊的大小,並返回一個包含給定大小的列表的列表。第一個塊從第一個元素開始並包含大小元素,第二個塊包含接下來的大小元素,依此類推。最後一個塊可能大小較小。

val numbers = (0..13).toList()
println(numbers.chunked(3))                // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13]]

你還可以立即對返回的塊應用轉換。為此,調用chunked()時提供轉換函數。lambda參數是集合的一個塊。當chunked()與轉換一起調用時,這些塊是短暫的列表,應該在該lambda中消費。

val numbers = (0..13).toList()
println(numbers.chunked(3) { it.sum() })   // [3, 12, 21, 30, 25]

窗口 (Windowed)

你可以檢索集合元素的所有可能的給定大小的範圍。用於獲取它們的函數稱為windowed():它返回元素範圍的列表,就像你通過滑動窗口查看集合一樣。與chunked()不同,windowed()返回從每個集合元素開始的元素範圍。所有窗口都作為單個列表的元素返回。

val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.windowed(3))               // [[one, two, three], [two, three, four], [three, four, five]]

windowed()提供了可選參數,使其更具靈活性:

  • step 定義了兩個相鄰窗口的第一個元素之間的距離。默認值為1,因此結果包含從所有元素開始的窗口。如果將步長增加到2,你將只會獲得從奇數元素開始的窗口:第一、第三,依此類推。
  • partialWindows 包含從集合末尾的元素開始的較小大小的窗口。例如,如果你請求三個元素的窗口,你無法為最後兩個元素構建它們。在這種情況下,啟用partialWindows將包含大小為2和1的兩個列表。

最後,你可以立即對返回的範圍應用轉換。為此,調用windowed()時提供轉換函數。

val numbers = (1..10).toList()
println(numbers.windowed(3, step = 2, partialWindows = true))  // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]]
println(numbers.windowed(3) { it.sum() })                      // [6, 9, 12, 15, 18, 21, 24, 27]

要構建兩元素的窗口,有一個單獨的函數——zipWithNext()。它創建接收集合的相鄰元素對。請注意,zipWithNext()不會將集合分成對;它為每個元素(除了最後一個)創建一個Pair,因此其結果對於[1, 2, 3, 4]是[[1, 2], [2, 3], [3, 4]],而不是[[1, 2], [3, 4]]。zipWithNext()也可以與轉換函數一起調用;該轉換函數應接受接收集合的兩個元素作為參數。

val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.zipWithNext())                                 // [(one, two), (two, three), (three, four), (four, five)]
println(numbers.zipWithNext { s1, s2 -> s1.length > s2.length }) // [false, false, false, true]

這些擴展函數使你能夠靈活地檢索和處理集合的部分內容。


【檢索單個元素】

Kotlin集合提供了一組函數,用於從集合中檢索單個元素。本頁描述的函數適用於列表和集合。

  • elementAt, elementAtOrNull, elementAtOrElse
    • get,  getOrNull, getOrElse
  • fitst, firstOrNull, find
  • last, lastOrNull, findLast
  • firstNotNullOfOrNull (map 使用)
  • random, randomOrNull

按位置檢索

要檢索特定位置的元素,可以使用elementAt()函數。調用該函數並傳入一個整數作為參數,你將獲得給定位置的集合元素。第一個元素的位置是0,最後一個元素的位置是size - 1

elementAt()對於不提供索引訪問的集合或靜態上未知提供索引訪問的集合很有用。在列表的情況下,更符合習慣的是使用索引訪問操作符(get()[])。

val numbers = linkedSetOf("one", "two", "three", "four", "five")
println(numbers.elementAt(3))  // four

val numbersSortedSet = sortedSetOf("one", "two", "three", "four")
println(numbersSortedSet.elementAt(0))  // one (元素按升序存儲)

還有一些用於檢索集合的第一個和最後一個元素的有用別名:first()last()

val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.first())  // one
println(numbers.last())   // five

為了避免在檢索不存在位置的元素時拋出異常,可以使用elementAt()的安全變體:

 

  • elementAtOrNull() 當指定位置超出集合範圍時返回null
  • elementAtOrElse() 另外接受一個lambda函數,該函數將一個整數參數映射為集合元素類型的實例。當調用超出範圍的位置時,elementAtOrElse()返回該lambda對給定值的結果。
val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.elementAtOrNull(5))  // null
println(numbers.elementAtOrElse(5) { index -> "The value for index $index is undefined" })
// The value for index 5 is undefined
  • elementAtOrNull = getOrNull
  • elementAtOrElse = getOrElse
val list = listOf(1,2,3,4)
   
println(list.getOrElse(1,{9}))	// 2
println(list.getOrElse(5,{9}))	// 9

println(list.getOrNull(1))	// 2
println(list.getOrNull(5))	// null

按條件檢索

函數first()last()也讓你可以搜索集合中符合特定條件的元素。當你調用帶有測試集合元素的謂詞的first()時,你將獲得符合條件為true的第一個元素。反過來,last()返回匹配條件的最後一個元素。

val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.first { it.length > 3 })  // three
println(numbers.last { it.startsWith("f") })  // five

如果沒有元素匹配,這兩個函數會拋出異常。為了避免這種情況,使用firstOrNull()lastOrNull():如果沒有找到匹配的元素,它們返回null

val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.firstOrNull { it.length > 6 })  // null

也可以使用別名:

  • find() 代替 firstOrNull()
  • findLast() 代替 lastOrNull()
val numbers = listOf(1, 2, 3, 4)
println(numbers.find { it % 2 == 0 })  // 2
println(numbers.findLast { it % 2 == 0 })  // 4

帶選擇器的檢索

如果你需要在檢索元素之前對集合進行映射,可以使用firstNotNullOf()函數。它結合了兩個操作:

  1. 使用map
  2. 返回結果中的第一個非空值

如果結果集合中沒有非空元素,firstNotNullOf()會拋出NoSuchElementException。使用對應的firstNotNullOfOrNull()來在這種情況下返回null

val list = listOf<Any>(0, "true", false)
// 將每個元素轉換為字符串並返回具有所需長度的第一個
val longEnough = list.firstNotNullOf { item -> item.toString().takeIf { it.length >= 4 } }
println(longEnough)  // true

隨機元素

如果你需要檢索集合的任意元素,調用random()函數。可以不帶參數調用它,或者將Random對象作為隨機性的來源。

val numbers = listOf(1, 2, 3, 4)
println(numbers.random())

在空集合上,random()會拋出異常。要接收null請使用randomOrNull()

檢查元素存在

  • contains
  • containsAll
  • isEmpty
  • isNotEmpty

要檢查集合中是否存在某個元素,使用contains()函數。如果有一個等於函數參數的集合元素,它返回true。可以使用in關鍵字以操作符形式調用contains()

要一次檢查多個實例的存在,調用containsAll()並將這些實例的集合作為參數。

val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.contains("four"))  // true
println("zero" in numbers)         // false

println(numbers.containsAll(listOf("four", "two")))  // true
println(numbers.containsAll(listOf("one", "zero")))  // false

此外,可以通過調用isEmpty()isNotEmpty()檢查集合中是否包含任何元素。

val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.isEmpty())       // false
println(numbers.isNotEmpty())    // true

val empty = emptyList<String>()
println(empty.isEmpty())         // true
println(empty.isNotEmpty())      // false

這些擴展函數使你能夠靈活地檢索和處理集合中的單個元素。


【排序】

元素的順序是某些集合類型的重要方面。例如,兩個具有相同元素的列表,如果它們的元素順序不同,它們就不相等

在Kotlin中,可以通過幾種方式定義對象的順序。

首先是自然順序。它是為實現Comparable介面的類型定義的。當沒有指定其他順序時,會使用自然順序來對其進行排序。

大多數內建類型是可比較的:

  • 數字類型使用傳統的數值順序:1大於0;-3.4f大於-5f,依此類推。
  • 字符和字符串使用詞典順序:b大於a;world大於hello。

要為自定義類型定義自然順序,請使該類型成為Comparable的實現者。這需要實現compareTo()函數。compareTo()必須接受同類型的另一個對象作為參數,並返回一個整數值,表示哪個對象更大:

  • 正值表示接收對象更大。
  • 負值表示它小於參數。
  • 零表示兩個對象相等。

以下是一個排序版本的類,該版本由主要部分和次要部分組成。

class Version(val major: Int, val minor: Int): Comparable<Version> {
    override fun compareTo(other: Version): Int = when {
        this.major != other.major -> this.major compareTo other.major // compareTo() 以中綴形式 
        this.minor != other.minor -> this.minor compareTo other.minor
        else -> 0
    }
}

fun main() {    
   	println(Version(1, 2))					// Version@12edcd21
    println(Version(1, 2) > Version(1, 3))  // false
    println(Version(2, 0) > Version(1, 5))  // true
}

自定義順序允許你以所需的方式對任何類型的實例進行排序。特別是,你可以為不可比較的對象定義順序,或為可比較類型定義自然順序以外的順序。要為某種類型定義自定義順序,請為其創建ComparatorComparator包含compare()函數:它接受一個類的兩個實例,並返回它們之間比較的整數結果。結果的解釋方式與上述compareTo()結果相同。

val lengthComparator = Comparator { str1: String, str2: String -> str1.length - str2.length }
println(listOf("aaa", "bb", "c").sortedWith(lengthComparator))  // [c, bb, aaa]

擁有lengthComparator,你可以按字符串長度排列,而不是按默認的詞典順序。

定義Comparator的一種更簡便的方法是使用標準庫中的compareBy()函數。compareBy()接受一個lambda函數,該函數從實例生成一個Comparable值,並將自定義順序定義為生成值的自然順序。

使用compareBy(),上面示例中的長度比較器如下所示:

println(listOf("aaa", "bb", "c").sortedWith(compareBy { it.length }))  // [c, bb, aaa]

Kotlin集合包提供了用於按自然順序、自定義順序甚至隨機順序排序集合的函數。在本頁中,我們將描述適用於只讀集合的排序函數。這些函數返回其結果作為包含原始集合元素的新集合,並按請求的順序排列。要了解有關原地排序可變集合的函數,請參見列表特定操作。

自然順序

基本函數sorted()sortedDescending()根據其自然順序返回升序和降序排序的集合元素。這些函數適用於Comparable元素的集合。

val numbers = listOf("one", "two", "three", "four")

println("Sorted ascending: ${numbers.sorted()}")        	 // Sorted ascending:  [four, one, three, two]
println("Sorted descending: ${numbers.sortedDescending()}")  // Sorted descending: [two, three, one, four]

自定義順序

對於自定義順序排序或不可比較對象排序,有sortedBy()sortedByDescending()函數。它們接受一個選擇器函數,該函數將集合元素映射為Comparable值,並按這些值的自然順序排序集合。

val numbers = listOf("one", "two", "three", "four")

val sortedNumbers = numbers.sortedBy { it.length }
println("Sorted by length ascending: $sortedNumbers")  // Sorted by length ascending: [one, two, four, three]
val sortedByLast = numbers.sortedByDescending { it.last() }
println("Sorted by the last letter descending: $sortedByLast")  // Sorted by the last letter descending: [four, two, one, three]

要為集合排序定義自定義順序,可以提供自己的Comparator。為此,調用sortedWith()函數並傳入你的Comparator。使用此函數,按字符串長度排序看起來如下所示:

val numbers = listOf("one", "two", "three", "four")
println("Sorted by length ascending: ${numbers.sortedWith(compareBy { it.length })}")  // Sorted by length ascending: [one, two, four, three]

反向順序

可以使用reversed()函數檢索反向順序的集合。

val numbers = listOf("one", "two", "three", "four")
println(numbers.reversed())  // [four, three, two, one]

reversed()返回包含元素副本的新集合。因此,如果你稍後更改原始集合,這不會影響以前獲得的reversed()結果。

另一個反轉函數 - asReversed()

返回相同集合實例的反轉視圖,因此如果原始列表不會更改,它可能比reversed()更輕量且更可取。

val numbers = listOf("one", "two", "three", "four")
    val reversedNumbers = numbers.asReversed()
    println(numbers)		  	// [one, two, three, four]
    println(numbers.reversed())	// [four, three, two, one]
    println(numbers)			// [one, two, three, four]
    println(reversedNumbers)  	// [four, three, two, one]

如果原始列表是可變的,它的所有更改都會反映在其反轉視圖中,反之亦然。

val numbers = mutableListOf("one", "two", "three", "four")
val reversedNumbers = numbers.asReversed()
println(reversedNumbers)  // [four, three, two, one]
numbers.add("five")
println(reversedNumbers)  // [five, four, three, two, one]

然而,如果列表的可變性未知或源根本不是列表,reversed()更可取,因為它的結果是未來不會更改的副本。

隨機順序

最後,有一個返回包含隨機順序的集合元素的新列表的函數——shuffled()。你可以不帶參數調用它,或將Random對象作為參數。

val numbers = listOf("one", "two", "three", "four")
println(numbers.shuffled())

這些函數使你能夠靈活地排序集合,並根據需要檢索其元素。


【聚合操作】

Kotlin集合包含一些常用的聚合操作函數——這些操作根據集合的內容返回單個值。大多數這些操作都是眾所周知的,並且在其他語言中也以相同方式工作:

  • minOrNull() 和 maxOrNull() 分別返回最小和最大的元素。對於空集合,它們返回null
  • average() 返回數字集合中元素的平均值。
  • sum() 返回數字集合中元素的總和。
  • count() 返回集合中的元素數量。
fun main() {
    val numbers = listOf(6, 42, 10, 4)

    println("Count: ${numbers.count()}")          // Count: 4
    println("Max: ${numbers.maxOrNull()}")        // Max: 42
    println("Min: ${numbers.minOrNull()}")        // Min: 4
    println("Average: ${numbers.average()}")      // Average: 15.5
    println("Sum: ${numbers.sum()}")              // Sum: 62
}
data class StudentGrade(val name: String, val subject: String, val grade: Int)

fun main() {
    val grades = listOf(
        StudentGrade("Alice", "Math", 85),
        StudentGrade("Alice", "Science", 90),
        StudentGrade("Bob", "Math", 75),
        StudentGrade("Bob", "Science", 80),
        StudentGrade("Charlie", "Math", 95),
        StudentGrade("Charlie", "Science", 100)
    )

    // 將成績根據學生姓名進行分組
    val gradesByStudent = grades.groupBy { it.name }

    // 對每個學生計算成績的統計數據
    for ((student, grades) in gradesByStudent) {
        val count = grades.count()
        val max = grades.maxByOrNull { it.grade }?.grade
        val min = grades.minByOrNull { it.grade }?.grade
        val average = grades.map { it.grade }.average()
        val sum = grades.sumOf { it.grade }

        println("Student: $student")
        println("  Count: $count")
        println("  Max: $max")
        println("  Min: $min")
        println("  Average: $average")
        println("  Sum: $sum")
        println()
    }
  
  /*
  Student: Alice
  Count: 2
  Max: 90
  Min: 85
  Average: 87.5
  Sum: 175

  Student: Bob
    Count: 2
    Max: 80
    Min: 75
    Average: 77.5
    Sum: 155

  Student: Charlie
    Count: 2
    Max: 100
    Min: 95
    Average: 97.5
    Sum: 195
	*/
  
}

還有一些函數可以通過特定的選擇器函數或自定義的Comparator來檢索最小和最大的元素:

  • maxByOrNull() 和 minByOrNull() 接受一個選擇器函數,並返回該函數返回值最大的或最小的元素。
  • maxWithOrNull() 和 minWithOrNull() 接受一個Comparator對象,並根據該Comparator返回最大或最小的元素。
  • maxOfOrNull() 和 minOfOrNull() 接受一個選擇器函數,並返回該選擇器函數的最大或最小返回值。
  • maxOfWithOrNull() 和 minOfWithOrNull() 接受一個Comparator對象,並根據該Comparator返回選擇器函數的最大或最小返回值。

這些函數在空集合上返回null。還有一些替代函數——maxOfminOfmaxOfWithminOfWith——它們與對應的函數作用相同,但在空集合上會拋出NoSuchElementException

val numbers = listOf(5, 42, 10, 4)
val min3Remainder = numbers.minByOrNull { it % 3 }
println(min3Remainder)  // 4

val strings = listOf("one", "two", "three", "four")
val longestString = strings.maxWithOrNull(compareBy { it.length })
println(longestString)  // three

多重屬性排序

compareBy 是一個 Kotlin 標準庫中的函數,用於創建比較器 (Comparator) 以便於對集合中的元素進行比較和排序。它可以根據一個或多個屬性來比較元素,並且可以輕鬆地用於各種排序操作。

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

val people = listOf(
    Person("Alice", 30),
    Person("Bob", 25),
    Person("Charlie", 30)
)

val sortedPeople = people.sortedWith(compareBy({ it.age }, { it.name }))
println(sortedPeople)
// [Person(name=Bob, age=25), Person(name=Alice, age=30), Person(name=Charlie, age=30)]
/*
改變升降冪排序可以這樣寫
age 升冪, name 降冪
*/
val sortedPeople = people.sortedWith(
        compareBy<Person> { it.age }
            .thenByDescending { it.name }
    )
// age 降冪, name 升冪
val sortedPeople = people.sortedWith(
        compareByDescending<Person> { it.age }
            .thenBy { it.name }
    )

除了常規的sum(),還有一個高級求和函數sumOf(),它接受一個選擇器函數並返回該函數應用於所有集合元素的總和。選擇器可以返回不同的數字類型:IntLongDoubleUIntULong(在JVM上還包括BigIntegerBigDecimal)。

val numbers = listOf(5, 42, 10, 4)
println(numbers.sumOf { it * 2 })           // 122
println(numbers.sumOf { it.toDouble() / 2 }) // 30.5

Fold和Reduce

對於更具體的情況,有reduce()fold()函數,它們將提供的操作依次應用於集合元素並返回累積結果。該操作接受兩個參數:先前的累積值和集合元素。

這兩個函數的區別在於fold()接受一個初始值,並在第一步中將其作為累積值,而reduce()的第一步使用第一個和第二個元素作為操作參數。

val numbers = listOf(5, 2, 10, 4)

val simpleSum = numbers.reduce { sum, element -> sum + element }	// 5 + 2 +10 + 4
println(simpleSum)  // 21

// 使用map做一樣的事
var simpleSum2 = 0 
    simpleSum2 = numbers.map { simpleSum2 += it}
        .let { 
            simpleSum2
        }	
    println(simpleSum2) // 21
      
val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 } // 0 + 5*2 + 2*2 + 10*2 + 4*2 
println(sumDoubled)  // 42

// 錯誤:第一個元素未在結果中加倍
// val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 }
// println(sumDoubledReduce)

  
  

上述示例顯示了區別:fold()用於計算加倍元素的總和。如果將相同的函數傳遞給reduce(),它將返回另一個結果,因為它在第一步中使用列表的第一個和第二個元素作為參數,因此第一個元素不會被加倍。

要對元素按相反順序應用函數,請使用reduceRight()foldRight()函數。它們的工作方式類似於fold()reduce(),但從最後一個元素開始,然後繼續到前一個。請注意,當從右到左進行折疊或歸約時,操作參數會改變順序:首先是元素,然後是累積值。

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

你還可以應用將元素索引作為參數的操作。為此,使用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)  // 15

val sumEvenRight = numbers.foldRightIndexed(0) { idx, element, sum -> if (idx % 2 == 0) sum + element else sum }
println(sumEvenRight)  // 14

所有的歸約操作在空集合上會拋出異常。要接收null,請使用它們的*OrNull()對應函數:

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

如果你希望保存中間累加值,請使用runningFold()(或其同義詞scan())和runningReduce()函數。

val numbers = listOf(0, 1, 2, 3, 4, 5)
val runningReduceSum = numbers.runningReduce { sum, item -> sum + item }
val runningFoldSum = numbers.runningFold(10) { sum, item -> sum + item }

println(runningReduceSum)  // [0, 1, 3, 6, 10, 15]
println(runningFoldSum)    // [10, 10, 11, 13, 16, 20, 25]

如果你需要在操作參數中使用索引,請使用runningFoldIndexed()runningReduceIndexed()


【Collections寫入操作】

可變集合支持更改集合內容的操作,例如添加移除元素。本頁將描述所有可變集合實現可用的寫操作。關於列表和映射的特定操作,請分別參見列表特定操作和映射特定操作。

添加元素

要向列表或集合添加單個元素,使用add()函數。指定的對象會附加到集合的末尾。

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

addAll()將參數對象的每個元素添加到列表或集合。參數可以是IterableSequenceArray。接收者和參數的類型可以不同,例如,可以將集合中的所有項目添加到列表中。

當在列表上調用時,addAll()按參數中的順序添加新元素。你也可以在調用addAll()時指定元素位置作為第一個參數。參數集合的第一個元素將插入到此位置。參數集合的其他元素將跟隨它,將接收者元素移到末尾。

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

你還可以使用原地版本的加號操作符plusAssign (+=)添加元素。當應用於可變集合時,+=將第二個操作數(元素或另一個集合)附加到集合的末尾。

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

移除元素

要從可變集合中移除元素,使用remove()函數。remove()接受元素值並移除此值的第一個出現。

val numbers = mutableListOf(1, 2, 3, 4, 3)
numbers.remove(3)  // 移除第一個`3`
println(numbers)  // [1, 2, 4, 3]
numbers.remove(5)  // 不移除任何東西
println(numbers)  // [1, 2, 4, 3]

要一次移除多個元素,有以下函數:

  • removeAll() 移除參數集合中的所有元素。或者,你可以將它與謂詞作為參數一起調用;在這種情況下,該函數會移除謂詞為true的所有元素。
  • retainAll() 是removeAll()的反向操作:它移除除參數集合之外的所有元素。當與謂詞一起使用時,它僅保留匹配謂詞的元素。
  • clear() 移除列表中的所有元素,並使其變空。
val numbers = mutableListOf(1, 2, 3, 4)
println(numbers)  // [1, 2, 3, 4]
numbers.retainAll { it >= 3 }
println(numbers)  // [3, 4]
numbers.clear()
println(numbers)  // []

val numbersSet = mutableSetOf("one", "two", "three", "four")
numbersSet.removeAll(setOf("one", "two"))
println(numbersSet)  // [three, four]

另一種從集合中移除元素的方法是使用減號操作符minusAssign (-=)——原地版本的minus。第二個參數可以是元素類型的單個實例或另一個集合。當右側是單個元素時,-=移除它的第一次出現。反之,如果它是一個集合,則會移除其元素的所有出現。例如,如果列表包含重複的元素,它們會一次性移除。第二個操作數可以包含集合中不存在的元素。這些元素不會影響操作的執行。

val numbers = mutableListOf("one", "two", "three", "three", "four")
numbers -= "three"
println(numbers)  // [one, two, three, four]
numbers -= listOf("four", "five")    
//numbers -= listOf("four")    // 與上面相同
println(numbers)  // [one, two, three]

更新元素

列表和映射還提供了用於更新元素的操作。它們在列表特定操作和映射特定操作中描述。對於集合,更新沒有意義,因為它實際上是移除一個元素並添加另一個元素。

這些操作使你能夠靈活地管理可變集合的內容,添加、移除和更新元素以滿足應用程序的需求。


【List特定操作】

列表是Kotlin中最受歡迎的內建集合類型。對列表元素的索引訪問提供了一套強大的操作。

按索引檢索元素

列表支持所有常見的元素檢索操作:elementAt()first()last()等。在列表中特定的是對元素的索引訪問,因此最簡單的讀取元素方式是通過索引檢索。這可以通過帶有索引作為參數的get()函數或簡寫的[index]語法來完成。

如果列表大小小於指定的索引,會拋出異常。有兩個其他函數可以幫助你避免這些異常:

  • getOrElse()允許你提供計算默認值的函數,以便在索引不存在時返回該值。
  • getOrNull()返回null作為默認值。
val numbers = listOf(1, 2, 3, 4)
println(numbers.get(0))               // 1
println(numbers[0])                   // 1
//numbers.get(5)                      // exception!
println(numbers.getOrNull(5))         // null
println(numbers.getOrElse(5) { it })  // 5

檢索列表部分

除了檢索集合部分的常見操作外,列表還提供了subList()函數,該函數返回指定元素範圍的視圖作為列表。因此,如果原始集合的元素發生變化,它也會在先前創建的子列表中變化,反之亦然。

val numbers = (0..13).toList()
println(numbers.subList(3, 6))  // [3, 4, 5]

查找元素位置

線性搜索

在任何列表中,你可以使用indexOf()lastIndexOf()函數查找元素的位置。它們返回列表中等於給定參數的元素的第一個和最後一個位置。如果沒有這樣的元素,這兩個函數返回-1

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

還有一對函數,它們接受謂詞並搜索匹配的元素:

  • indexOfFirst()返回匹配謂詞的第一個元素的索引,如果沒有這樣的元素則返回-1。
  • indexOfLast()返回匹配謂詞的最後一個元素的索引,如果沒有這樣的元素則返回-1。
val numbers = mutableListOf(1, 2, 3, 4)
println(numbers.indexOfFirst { it > 2})   // 2
println(numbers.indexOfLast { it % 2 == 1})  // 2
有序列表中的二分搜索

還有一種在列表中搜索元素的方法——二分搜索。它比其他內建搜索函數快得多,但要求列表按某個順序升序排序:自然順序或函數參數中提供的其他順序。否則,結果是未定義的。

要在排序列表中搜索元素,調用binarySearch()函數並傳遞值作為參數。如果存在這樣的元素,該函數返回其索引;否則,它返回(-insertionPoint - 1),其中insertionPoint是應插入此元素以保持列表排序的索引。如果有多個具有給定值的元素,搜索可以返回其中任何一個的索引。

你還可以指定一個索引範圍進行搜索:在這種情況下,該函數僅在提供的兩個索引之間搜索。

val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort()
println(numbers)  // [four, one, three, two]
println(numbers.binarySearch("two"))  // 3
println(numbers.binarySearch("z")) // -5
println(numbers.binarySearch("two", 0, 2))  // -3
使用Comparator的二分搜索

當列表元素不可比較時,你應該提供一個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的價格,則p1p2之前。因此,擁有按此順序升序排列的列表,我們使用binarySearch()來查找指定Product的索引。

當列表使用不同於自然順序的順序時,自定義比較器也很方便,例如,對字符串元素使用不區分大小寫的順序。

val colors = listOf("Blue", "green", "ORANGE", "Red", "yellow")
println(colors.binarySearch("RED", String.CASE_INSENSITIVE_ORDER)) // 3
使用比較函數的二分搜索

使用比較函數進行二分搜索可以讓你在不提供顯式搜索值的情況下查找元素。相反,它接受一個將元素映射為Int值的比較函數,並搜索該函數返回零的元素。列表必須按所提供函數的升序排序;換句話說,從一個列表元素到下一個列表元素,比較返回值必須遞增。

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) })  // 1
}

比較器和比較函數的二分搜索也可以在列表範圍內執行。

List寫入操作

除了在集合寫操作中描述的集合修改操作外,可變列表還支持特定的寫操作。這些操作使用索引訪問元素,以擴展列表的修改功能。

添加

要將元素添加到列表的特定位置,使用add()addAll()並提供元素插入位置作為附加參數。所有位於該位置之後的元素將右移。

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

列表還提供了替換給定位置的元素的函數——set()及其操作符形式[]set()不會改變其他元素的索引。

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

fill()簡單地將所有集合元素替換為指定值。

val numbers = mutableListOf(1, 2, 3, 4)
numbers.fill(3)
println(numbers)  // [3, 3, 3, 3]
移除

要從列表中移除特定位置的元素,使用removeAt()函數並提供位置作為參數。被移除元素之後的所有元素索引將減少一。

val numbers = mutableListOf(1, 2, 3, 4, 3)    
numbers.removeAt(1)
println(numbers)  // [1, 3, 4, 3]
排序

在集合排序中,我們描述了按特定順序檢索集合元素的操作。對於可變列表,標準庫提供了類似的擴展函數,這些函數執行相同的排序操作。當你將這樣的操作應用於列表實例時,它會改變該實例中元素的順序

原地排序函數的名稱與適用於只讀列表的函數名稱相似,但沒有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")  // [four, one, three, two]
numbers.sortDescending()
println("Sort into descending: $numbers")  // [two, three, one, four]

numbers.sortBy { it.length }
println("Sort into ascending by length: $numbers")  // [one, two, four, three]
numbers.sortByDescending { it.last() }
println("Sort into descending by the last letter: $numbers")  // [four, two, one, three]

numbers.sortWith(compareBy<String> { it.length }.thenBy { it })
println("Sort by Comparator: $numbers")  // [one, two, four, three]

numbers.shuffle()
println("Shuffle: $numbers")  // [three, one, two, four]

numbers.reverse()
println("Reverse: $numbers")  // [four, two, one, three]

這些操作使你能夠靈活地管理和修改列表中的元素,以滿足各種需求。


【Set特定操作】

Kotlin集合包中包含了一些常見操作的擴展函數,如查找交集、合併或從另一個集合中減去集合。

合併集合

要將兩個集合合併為一個集合,使用union()函數。它可以以中綴形式使用,即a union b。注意,對於有序集合,操作數的順序很重要。在結果集合中,第一個操作數的元素排在第二個操作數的元素之前:

val numbers = setOf("one", "two", "three")

// 按順序輸出
println(numbers union setOf("four", "five"))
// [one, two, three, four, five]
println(setOf("four", "five") union numbers)
// [four, five, one, two, three]

查找交集(intersect)和差集(subtract)

要查找兩個集合之間的交集(即同時存在於兩個集合中的元素),使用intersect()函數。要查找不在另一個集合中的集合元素,使用subtract()函數。這兩個函數也可以以中綴形式調用,例如a intersect b

val numbers = setOf("one", "two", "three")

// 相同輸出
println(numbers intersect setOf("two", "one"))
// [one, two]
println(numbers subtract setOf("three", "four"))
// [one, two]
println(numbers subtract setOf("four", "three"))
// [one, two]

查找對稱差

要查找存在於兩個集合中的任一集合但不在其交集中存在的元素(即對稱差),你可以使用union()函數。對於這種操作,計算兩個集合之間的差異並合併結果:

val numbers = setOf("one", "two", "three")
val numbers2 = setOf("three", "four")

// 合併差異
println((numbers - numbers2) union (numbers2 - numbers))
// [one, two, four]

List 的集合操作

你也可以將union()intersect()subtract()函數應用於列表。在這個結果中,所有的重複元素都會合併為一個,且無法進行索引訪問:

val list1 = listOf(1, 1, 2, 3, 5, 8, -1)
val list2 = listOf(1, 1, 2, 2, 3, 5)

// 兩個列表相交的結果是一個集合
println(list1 intersect list2)
// [1, 2, 3, 5]

// 相同元素合併為一個
println(list1 union list2)
// [1, 2, 3, 5, 8, -1]
data class Person(var name:String,var age:Int)

fun main() {    
   val list1 = listOf(Person("A",11),Person("B",12),Person("C",13))
   val list2 = listOf(Person("B",14),Person("C",13))

   println(list1 intersect list2)
   // [Person(name=C, age=13)]
   println(list1 union list2)
   // [Person(name=A, age=11), Person(name=B, age=12), Person(name=C, age=13), Person(name=B, age=14)]
}

這些操作使你能夠靈活地管理和操作set,以滿足不同的需求。


【Map 特定操作】

在 Map 中,鍵和值的類型是用戶定義的。基於鍵的訪問使得 Map 特定的處理功能變得可能,從通過鍵獲取值到分別過濾鍵和值。在本頁中,我們將介紹標準庫中的 Map 處理函數。

檢索鍵和值

要從 Map 中檢索值,必須提供鍵作為 get() 函數的參數。也支持簡寫的 [key] 語法。如果找不到給定的鍵,則返回 null。還有一個 getValue() 函數,其行為略有不同:如果在 Map 中找不到鍵,則會拋出異常。此外,還有兩個選項來處理鍵的缺失:

  • getOrElse() 的工作方式與列表相同:不存在鍵的值從給定的 lambda 函數返回。
  • getOrDefault() 在找不到鍵時返回指定的默認值。
val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
println(numbersMap.get("one"))          // 1
println(numbersMap["one"])              // 1
println(numbersMap.getOrDefault("four", 10))  // 10
println(numbersMap["five"])             // null
//numbersMap.getValue("six")            // exception!

要對 Map 的所有鍵或所有值執行操作,可以分別從 keys 和 values 屬性中檢索它們。keys 是一組所有 Map 鍵,values 是所有 Map 值的集合。

val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
println(numbersMap.keys)                // [one, two, three]
println(numbersMap.values)              // [1, 2, 3]

過濾

  •  filterKeys
  • filterValues

你可以使用 filter() 函數來過濾 Map,與其他集合一樣。當對 Map 調用 filter() 時,將一個帶有 Pair 作為參數的謂詞(predicate)傳遞給它。這使得你可以在過濾謂詞中同時使用鍵和值。

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)  // {key11=11}

還有兩種特定的過濾 Map 的方法:按鍵和按值。對於每種方法,都有一個函數:filterKeys() 和 filterValues()。這兩個函數都返回一個新的 Map,其中包含匹配特定條件的 entries。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)  // {key1=1, key11=11}
println(filteredValuesMap)  // {key1=1, key2=2, key3=3}

加號和減號操作符

由於基於鍵訪問元素,加號 (+) 和減號 (-) 操作符在 Map 中的工作方式與其他集合不同。加號返回一個包含其兩個操作數元素的 Map:左側的 Map 和右側的 Pair 或另一個 Map。當右側操作數包含鍵存在於左側 Map 中的 entries 時,結果 Map 包含來自右側的 entries。

val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
println(numbersMap + Pair("four", 4))  // {one=1, two=2, three=3, four=4}
println(numbersMap + Pair("one", 10))  // {one=10, two=2, three=3}
println(numbersMap + mapOf("five" to 5, "one" to 11))  // {one=11, two=2, three=3, five=5}

減號從左側 Map 中的 entries 創建一個 Map,除了右側操作數中的鍵。所以,右側操作數可以是單個鍵或鍵的集合:列表、集合等。

val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
println(numbersMap - "one")  // {two=2, three=3}
println(numbersMap - listOf("two", "four"))  // {one=1, three=3}

有關在可變 Map 上使用加號賦值 (+=) 和減號賦值 (-=) 操作符的詳細信息,請參見下面的 Map 寫操作。

Map 寫操作

可變 Map(Mutable maps) 提供 Map 特定的寫操作。這些操作允許你使用基於鍵的訪問來更改 Map 內容。

有些規則定義了 Map 上的寫操作:

  • 值可以更新。反之,鍵永遠不變:一旦添加 entry,其鍵是固定的
  • 對於每個鍵,總是有一個與之關聯的單個值。你可以添加和移除整個 entry。

以下是標準庫中可變 Map 上可用的寫操作函數的描述。

添加和更新 entry

要向可變 Map 添加新的鍵值對,使用 put()。當將新 entry 放入 LinkedHashMap(默認 Map 實現)時,它會被添加,使其在迭代 Map 時出現在最後。在排序 Map 中,新元素的位置由其鍵的順序定義。

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

要一次添加多個 entry,使用 putAll()。其參數可以是 Map 或一組 PairIterableSequence 或 Array

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

put() 和 putAll() 都會覆蓋已存在鍵的值。因此,你可以使用它們來更新 Map entries 的值。

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)  // {one=11, two=2}

你也可以使用簡寫操作符形式向 Map 添加新 entry。有兩種方式:

  • plusAssign (+=) 操作符。
  • [] 操作符的 set() 別名。
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap["three"] = 3     // 調用 numbersMap.put("three", 3)
numbersMap += mapOf("four" to 4, "five" to 5)
println(numbersMap)  // {one=1, two=2, three=3, four=4, five=5}

當鍵存在於 Map 中時,操作符會覆蓋相應 entry 的值。

移除 entry

要從可變 Map 中移除 entry,使用 remove() 函數。調用 remove() 時,可以傳遞鍵或整個鍵值對。如果同時指定了鍵和值,則只有當值與第二個參數匹配時,該鍵的元素才會被移除。

val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
numbersMap.remove("one")
println(numbersMap)  // {two=2, three=3}
numbersMap.remove("three", 4)  // 不移除任何東西
println(numbersMap)  // {two=2, three=3}

你也可以根據鍵或值從可變 Map 中移除 entries。要做到這一點,調用 remove() 在 Map 的 `

keysvalues中提供鍵或值。如果調用valuesremove()` 只會移除第一個與給定值匹配的 entry。

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

minusAssign (-=) 操作符同樣適用於可變 Map。

val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
numbersMap -= "two"
println(numbersMap)  // {one=1, three=3}
numbersMap -= "five"  // 不移除任何東西
println(numbersMap)  // {one=1, three=3}

【其他】List 如何轉為 Set

在 Kotlin 中,可以使用多種方法將 List 轉換為 SetSet 是一種不允許重複元素的集合,因此轉換過程中會去除重複元素。以下是一些常用的方法:

1. 使用 toSet 函數

toSet 函數是一個簡便的方法,直接將 List 轉換為 Set

範例:

val list = listOf(1, 2, 2, 3, 4, 4, 5)
val set = list.toSet()
println(set) // [1, 2, 3, 4, 5]
2. 使用 setOf 函數

setOf 函數可以用於創建一個 Set,接受可變參數的元素列表。可以將 List 展開後作為參數傳遞給 setOf

範例:

val list = listOf(1, 2, 2, 3, 4, 4, 5)
val set = setOf(*list.toTypedArray())
println(set) // [1, 2, 3, 4, 5]
3. 使用 HashSet 構造函數

可以使用 HashSet 的構造函數來創建一個 Set,並將 List 作為參數傳遞給它。

範例:

val list = listOf(1, 2, 2, 3, 4, 4, 5)
val set = HashSet(list)
println(set) // [1, 2, 3, 4, 5]
4. 使用 asSequence 並轉換為 Set

如果想要在轉換過程中應用一些其他操作,可以使用 asSequence,然後再轉換為 Set

範例:

val list = listOf(1, 2, 2, 3, 4, 4, 5)
val set = list.asSequence().toSet()
println(set) // [1, 2, 3, 4, 5]

【其他】比較 groupBy, groupingBy 和 partition

在 Kotlin 中,groupBygroupingBy 和 partition 是用來處理集合的方法,這些方法可以幫助我們更有效地對數據進行分組和劃分。以下是這些方法的詳細說明:

1. groupBy

groupBy 方法將集合中的元素根據指定的鍵進行分組(分成多組),並返回一個 Map,其中鍵是根據元素計算出來的,值是對應於該鍵的元素列表。

語法:

fun <T, K> Iterable<T>.groupBy(
    keySelector: (T) -> K
): Map<K, List<T>>

範例:

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

val people = listOf(
    Person("Alice", 25),
    Person("Bob", 30),
    Person("Charlie", 25),
    Person("David", 30)
)

val groupedByAge = people.groupBy { it.age }
println(groupedByAge)
// Output: {25=[Person(name=Alice, age=25), Person(name=Charlie, age=25)], 30=[Person(name=Bob, age=30), Person(name=David, age=30)]}
2. groupingBy

在 Kotlin 中,groupingBy 是一個很有用的擴展函數,用於將一個集合的元素根據某個鍵(key)進行分組。這個函數返回一個 Grouping 物件,這個物件提供了各種操作來對分組結果進行進一步處理,比如計數、聚合等。

以下是一個使用 groupingBy 的簡單範例:

Grouping 物件提供了一些函數來對分組結果進行進一步的操作:

  • eachCount(): 計算每個分組中的元素個數,返回一個 Map<K, Int>,鍵是分組鍵,值是該組的元素個數。

  • fold(initialValue, operation): 針對每一組應用 operation 函數,並以 initialValue 作為初始值進行累積操作。

  • reduce(operation): 針對每一組應用 operation 函數進行累積操作。

  • aggregate(initializer, operation): 與 fold 類似,但提供更靈活的操作方式,可以根據需要初始化和累積。

語法:

fun <T, K> Iterable<T>.groupingBy(
    keySelector: (T) -> K
): Grouping<T, K>

eachCount 範例:

val words = listOf("one", "two", "three", "four", "five", "six")

val grouping = words.groupingBy { it.length }
val countByLength = grouping.eachCount()

println(countByLength)
// Output: {3=2, 4=3, 5=1}

fold 範例:

val fruits = listOf("apple", "banana", "apricot", "blueberry", "avocado")

val lengthSumByInitial = fruits.groupingBy { it.first() }
    .fold(0) { acc, e -> acc + e.length }

println(lengthSumByInitial) // {a=17, b=15}

在這個範例中,我們將 fruits 列表中的字串根據第一個字母分組,然後對每個組的字串長度進行累加。

reduce 範例:

val numbers = listOf(1, 2, 3, 4, 5, 6)

val maxByRemainder = numbers.groupingBy { it % 3 }
    .reduce { _, a, b -> if (a > b) a else b }

println(maxByRemainder) // {1=4, 2=5, 0=6}

在這個範例中,numbers 列表根據元素除以 3 的餘數進行分組,並在每組中選出最大的數字。

groupingBy 是 Kotlin 集合操作中一個強大而靈活的工具,適用於需要按鍵分組並進行後續處理的情況。透過這個函數,我們可以輕鬆地對集合中的元素進行複雜的分組和聚合操作。

3. partition

partition 方法將集合分成兩個列表,第一個列表包含符合給定條件的元素,第二個列表包含不符合條件的元素。

語法:

fun <T> Iterable<T>.partition(
    predicate: (T) -> Boolean
): Pair<List<T>, List<T>>

範例:

val numbers = listOf(1, 2, 3, 4, 5, 6)

val (even, odd) = numbers.partition { it % 2 == 0 }
println(even) // Output: [2, 4, 6]
println(odd)  // Output: [1, 3, 5]
總結
  • groupBy:用於將集合分組成一個 Map,鍵是分組的依據,值是分組後的列表。
  • groupingBy:返回一個 Grouping 物件,允許進一步的分組操作。
  • partition:將集合分為兩部分,一部分符合條件,另一部分不符合條件。

【其他】常見命名

xxxWith(): 傳入物件做某些事

xxxBy{}: 傳入lambda做某件事

xxx/xxxed: ed回傳新物件沒有ed 直接修改原物件 

 val list = mutableListOf("bb", "c", "aaa")
 println(list)										// [bb, c, aaa]
 println(list.sortedWith(compareBy { it.length }))  // [c, bb, aaa]
 println(list.sortedBy{it.length})					// [c, bb, aaa]
 list.sorted()
 println(list)	// [bb, c, aaa]
 list.sort()
 println(list)	// [aaa, bb, c]

xxxTo():寫入物件

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

  val tmpMap = mutableListOf<String>("aa")
  val tmpMap2 = mutableListOf<String>("bb")
  println(numbers.mapTo(tmpMap){it})
  // [aa, one, two, three, four, five]
  println(numbers.filterTo(tmpMap2){it.length > 3})
  // [bb, three, four, five]