【Kotlin】第5課-集合(List、Set、Map)

集合跟陣列類似,都是可以儲存許多資料的一種物件。差別在於集合的容量不需定義,也沒有上限。集合與 Java 一樣,有 List、Set 與 Map 三大類,在 Kotlin 中,又分成唯讀和可讀寫的類型。本文就來認識它們。

一、唯讀集合

唯讀代表宣告集合時需同時定義元素,之後就不能再增刪元素。資料型態包含「List」、「Set」與「Map」,它們都是介面(interface),並沒有提供有關增刪元素的方法。

// 唯讀 List
val list1: List<Int> = listOf<Int>()
val list2: List<Int> = listOf(80)
val list3: List<Int> = listOf(1, 2, 3)

// 唯讀 Set
val set1: Set<Int> = setOf<Int>()
val set2: Set<Int> = setOf(80)
val set3: Set<Int> = setOf(1, 2, 3)

// 唯讀 Map
val map1: Map<String, Int> = mapOf<String, Int>()
val map2: Map<String, Int> = mapOf("Anna" to 10)
val map3: Map<String, Int> = mapOf("Anna" to 10, "Bonnie" to 20, "Cynthia" to 30)

宣告唯讀集合時,隨著元素個數不同,集合的本質(實作類別)也不同。

  1. 空集合:本質為 EmptyListEmptySetEmptyMap沒有元素能存取。
  2. 單一元素集合:本質為 Java 的 SingletonListSingletonSetSingletonMap,裡頭只有一個元素可存取。
  3. 多元素集合:本質為 Java 的 ArrayListLinkedHashSetLinkedHashMap元素排列順序等同定義順序


二、可讀寫集合

可讀寫代表宣告集合時,未必要定義初始元素,且之後允許增刪元素。資料型態包含 MutableListMutableSetMutableMap。它們都是介面,分別繼承自 List、Set 與 Map,具備有關增刪元素的方法。

宣告可讀寫集合時,建立出來的集合物件的本質,不會因元素個數而有不同。均與第一節介紹的唯讀多元素集合相同。

// 本質為 Java 的 ArrayList
val list1: MutableList<Int> = mutableListOf()
val list2: List<Int> = mutableListOf(2, 1, 3)

// 本質為 Java 的 LinkedHashSet
val set1: MutableSet<Int> = mutableSetOf()
val set2: Set<Int> = mutableSetOf(2, 1, 3)

// 本質為 Java 的 LinkedHashMap
val map1: MutableMap<String, Int> = mutableMapOf()
val map2: Map<String, Int> = mutableMapOf("Anna" to 10, "Bonnie" to 20, "Cynthia" to 30)

三、存取元素

新增與讀取集合元素的方式如下。其中 List 與 Map 可使用陣列索引的做法,也能選擇 getsetput 方法。

// List 使用 add 新增元素、並用索引存取
val myList = mutableListOf<Int>()
myList.add(350)
myList[0] += 30
println(myList[0])

// Set 使用 add 新增元素
val mySet = mutableSetOf<String>()
mySet.add("Anna")

// Map 使用索引新增和讀取鍵值
val myMap = mutableMapOf<String, Int>()
myMap["Anna"] = 350
myMap["Anna"] = myMap["Anna"]!! + 30 // 鍵值不存在會取得 null,需特別注意
println(myMap["Anna"])

走訪集合元素的方式跟陣列也有幾分相似,包括 for each、indices 方法與 withIndex 方法。但不是每種集合都能適用這三種。

List 與陣列最相近,走訪方式包括 for each、indiceswithIndex

// 使用 for each
val girls = listOf("Anna", "Bonnie", "Cynthia")
for (name in girls) {
println(name)
}

// 使用索引 (indices)
val boys = listOf("Darcy", "Edison")
for (i in boys.indices) {
println(boys[i])
}

Set 的走訪方式包括 for each 與 withIndex。但 withIndex 所帶來的索引,無法像 List 能用來取得元素,因此只剩下流水號的功用。

// 使用 for each
val girls = setOf("Bonnie", "Anna", "Cynthia")
for (name in girls) {
println(name)
}

// 使用 withIndex
val boys = setOf("Darcy", "Edison")
for ((i, v) in boys.withIndex()) {
println("${i + 1}. $v")
}

Map 的走訪方式僅有 for each。可以在迴圈中接收 key 與 value,或接收 Map.Entry 物件。

// 接收key與value
val girls = mapOf("Anna" to 406, "Bonnie" to 408, "Cynthia" to 412)
for ((k, v) in girls) {
println("$k: $v")
}

// 接收Entry物件
val boys = mapOf("Darcy" to 430, "Edison" to 414)
for (entry in boys) {
println("${entry.key}: ${entry.value}")
}


四、其他 Java 原生集合

以上是使用 Kotlin 提供的方法,來宣告本質為 Java 原生的集合。本節再介紹一些方法,可以建立 Java 的其它常見集合。

// List尚有 ArrayList
val list: ArrayList<Int> = arrayListOf<Int>()

// Set尚有 HashSet、LinkedSet 與 SortedSet
val set: HashSet<Int> = hashSetOf<Int>()

// Map尚有 HashMap、LinkedMap 與 SortedMap
val map: HashMap<String, Int> = hashMapOf<String, Int>()

以上的 Linked、Sorted 系列集合,本質分別對應 Java 的 Linked 與 Tree 系列集合。筆者就不示範使用方法,讀者可以自行探索。

Kotlin 集合的部份原始碼


五、綜合應用

結合本文所學,以下提供一個練習,讀者可以嘗試用程式解決問題。下圖給定了三個 Map,存放著五位學生的的國文、英文與數學成績,但部份學生缺考。請撰寫一個方法,計算他們的總分(缺少的分數以零分計),接著輸出學生名字與總分,並照名字遞增排序。

fun main(args: Array<String>) {
// 給定各科目成績資料
val chineseScore =
mapOf("Anna" to 78, "Bonnie" to 56,
"Darcy" to 72, "Edison" to 68)
val englishScore =
mapOf("Bonnie" to 96, "Cynthia" to 82,
"Darcy" to 84, "Edison" to 90)
val mathScore =
mapOf("Anna" to 88, "Bonnie" to 84,
"Cynthia" to 92, "Darcy" to 92)

printTranscript(chineseScore, englishScore, mathScore)
}

讀者可參考下方筆者的做法。

fun printTranscript(vararg scores: Map<String, Int>) {
val students = mutableSetOf<String>()
for (score in scores) {
students.addAll(score.keys)
}

val transcript = sortedMapOf<String, Int>()

for (stu in students) {
var total = 0
for (score in scores) {
total += score.getOrDefault(stu, 0)
}

transcript[stu] = total
}

for ((k, v) in transcript) {
println("$k: $v")
}
}

方法使用可變長度參數,這樣一旦有新的科目分數,只要簡單地傳送進來即可。在方法中,首先從參數的 Map 陣列,取得所有學生的名字。由於是存放在 Set 中,所以不會重複

接著走訪所有學生名字,再用來查詢各科目的分數,並逐一加總。如果缺少分數,預設為零分。加總完一個學生的分數後,再存放到另一個 Map 中。

由於輸出時需要排序,SortedMap 正好能符合按照 key 遞增排序的條件,因此選用它。最後走訪 Map,輸出所有學生的總分。

上一篇:【Kotlin】第4課-陣列(Array)

下一篇:【Kotlin】第6課-類別的設計(一)

留言