集合的分組(Grouping)
在之前的學習中,我們已經學會了如何對集合進行過濾、排序或執行聚合操作。
在本節中,我們將學習如何對集合元素進行分組,以便以最適合我們任務的方式呈現信息。
分組(Grouping)
在 Kotlin 中,有一些擴展函數可以用來對集合元素進行分組,其中一個就是 groupBy()
。它接收一個 lambda 表達式,并返回一個 Map,其中的鍵(key)是分組依據,值(value)則是對應的集合元素組成的列表。
fun main() {val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")// 按名字的首字母進行分組val groupedNames = names.groupBy { it.first() }println(groupedNames) // {J=[John, Jane, John, Jane], M=[Mary, Mary], P=[Peter, Peter]}
}
代碼解釋:
在上面的示例中,我們按名字的首字母進行分組:可以看到返回的 Map 中,鍵是名字的首字母,值是所有以該字母開頭的名字列表。例如,鍵 J
對應的值是 [John, Jane, John, Jane]
。
你還可以在 groupBy()
中傳入第二個 lambda 作為轉換函數(valueTransform)。這樣就能在分組時同時對元素進行變換。如下所示,我們將分組的元素轉換為大寫:
fun main() {val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")// 按名字長度分組,并將每個名字轉為大寫fun main() {val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")// 按名字長度分組,并將每個名字轉為大寫?val groupedNames2 = names.groupBy(keySelector = { it.length },valueTransform = { it.uppercase() })println(groupedNames2) // {4=[JOHN, JANE, MARY, JOHN, JANE, MARY], 5=[PETER, PETER]}
}val groupedNames2 = names.groupBy(keySelector = { it.length }, // 分組鍵:名字長度valueTransform = { it.uppercase() } // 值變換:轉為大寫)println(groupedNames2) // 輸出:{4=[JOHN, JANE, MARY, JOHN, JANE, MARY], 5=[PETER, PETER]}
}
分組與附加操作
有時我們想對所有分組同時進行某種操作。我們可以使用 groupingBy()
方法,它返回一個 Grouping 實例,允許以“懶方式”對各組進行操作(即在執行操作前不會真正構建分組)。
常見方法:
-
eachCount()
:計算每組中元素的數量,返回一個 Map,鍵是分組鍵,值是該組的元素數量。 -
fold()
:帶有初始值,從左到右依次將操作應用于累加器與每個元素。如果集合為空,返回初始值。
可以提供一個initialValueSelector
函數用于設置初始值。.fold(initialValue) { acc, element -> ... }
-
reduce()
:從第一元素開始進行累加操作(沒有初始值),如果集合為空會拋出異常。可使用reduceOrNull()
以防止異常。.groupingBy { ... }.reduce { key, acc, element -> ... }
fun main() {val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")// 按首字母分組,并統計每組數量val groupedNames3 = names.groupingBy { it.first() }.eachCount()println(groupedNames3) // {J=4, M=2, P=2}// 按首字母分組,累加每組中名字的總長度val groupedNames4 = names.groupingBy { it.first() }.fold(0) { acc, name -> acc + name.length }println(groupedNames4) // {J=16, M=8, P=10}// 按名字長度分組,保留每組中最長的名字val groupedNames5 = names.groupingBy { it.length }.reduce { _, acc, name -> if (name.length > acc.length) name else acc }println(groupedNames5) // {4=John, 5=Peter}
}
使用 aggregate() 聚合
使用 aggregate()
函數,可以對每個分組應用操作并返回結果。它提供了一種通用方式來執行分組操作(在 fold
和 reduce
不滿足需求時)。
語法:
aggregate { key, accumulator: R?, element, first ->// 返回新的 accumulator(累積值)
}
-
key
:當前分組的鍵,比如J
,M
,P
。 -
accumulator
:上一次累積的值,第一次為null
。 -
element
:當前正在處理的元素(比如"John"
)。 -
first
:是否是該組的第一個元素。
示例:
fun main() {val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")// 使用 aggregate 獲取每組的元素數量val groupedNames6 = names.groupingBy { it.first() }.aggregate { _, accumulator: Int?, _, first ->if (first) 1 else accumulator!! + 1}println(groupedNames6) // {J=4, M=2, P=2}// 判斷每組中所有名字的長度是否都是偶數val groupedNames7 = names.groupingBy { it.first() }.aggregate { _, accumulator: Boolean?, element, first ->if (first) element.length % 2 == 0 else accumulator!! && element.length % 2 == 0}println(groupedNames7) // {J=true, M=true, P=false}
}
總結
在本節中,我們學習了如何使用 groupBy
和 groupingBy
函數對集合中的元素進行分組。這是處理集合數據時非常重要的技能。