Kotlin Programming 2022 Spring - Lecture 6 === ###### tags: `Kotlin 2022 Spring` `Kotlin` `2022 Spring` This lecture intends to cover the materials in Chapter 10 and Chapter 11. However, the lecturer encourage you to read the [official documents about collections](https://kotlinlang.org/docs/reference/collections-overview.html). ## List + Ordered collection of elements + Repeated values are allowed + Elements have the same type + `List<Int>` + `List<Double>` + `List<String>` + `String.split()` returns `List<String>` + `List<Any>` + `List<List<Int>>` ```kotlin fun main() { val listInt = listOf(1, 2, 3) val listDouble = listOf(1.0, 2.0, 3.0) val listString = listOf("a", "bc", "xyz") val listAny = listOf(1, 2.0, "3") val listListInt = listOf(listOf(1, 2), listOf(3, 4)) println(listInt) println(listDouble) println(listString) println(listAny) println(listListInt) } ``` + Indexed by integer: zero based + `.size` is the number of elements + Use `.get(i)` or `[i]` to index + [`.getOrNull(i)`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/get-or-null.html) returns `null` if `i` is invalid. + [`.getOrElse(i){expression}`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/get-or-else.html) returns `expression` if `i` is invalid. + Use `.last()` to access the last element + `.indexOf()` and `.lastIndexOf()` ```kotlin fun main() { val listInt = listOf(1, 2, 3, 2, 3, 2, 5) println("$listInt has size ${listInt.size}") println("listInt.get(0) is ${listInt.get(0)}") println("listInt[1] is ${listInt[1]}") println("listInt.last() is ${listInt.last()}") println("The first index of 2 is ${listInt.indexOf(2)}") println("The last index of 2 is ${listInt.lastIndexOf(2)}") // should crash println("listInt[listInt.size] is ${listInt[listInt.size]}") // should be null println("listInt.getOrNull(listInt.size) is ${listInt.getOrNull(listInt.size)}") // should be listInt.size * listInt.size println("listInt.getOrElse(listInt.size){it * it} is ${listInt.getOrElse(listInt.size){it * it}}") } ``` + Create a list + `listOf` + `List(size){expression}` -- [reference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list.html) ```kotlin fun main() { val numbers = listOf(1,2,3,4,5,6,7,8,9,10) println(numbers) val reversedNumbers = List(10){10 - it} println(reversedNumbers) } ``` + `for`-loop + `.withIndex()` -- [reference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/with-index.html) ```kotlin fun main() { val listInt = listOf(1, 2, 3) println("listInt contains:") for (element in listInt) { println(element) } println("If you need indexes, use .withIndex().") for ((index,element) in listInt.withIndex()) { println("listInt[$index] is $element") } } ``` + `contains` and `containsAll` ```kotlin fun main() { val listInt = listOf(1, 2, 3, 3) val x = 3 if (listInt.contains(x)) println("$listInt contains $x") val y = 5 if (listInt.contains(y)) println("$listInt contains $y") val z = listOf(1,2,2,3) if (listInt.containsAll(z)) println("$listInt contains all $z") } ``` + Transform, filter, aggregate ```kotlin fun main() { val toks = "1 3 1 224 113".split(" ") println("toks is $toks") println("toks.first()+toks.last() is ${toks.first()+toks.last()}") val ints = toks.map{it.toInt()} println("ints is $ints") println("ints.first()+ints.last() is ${ints.first()+ints.last()}") val oddInts = ints.filter{it % 2 != 0} println("oddInts is $oddInts") println("oddInts.sum() is ${oddInts.sum()}") println("oddInts.max() is ${oddInts.max()}") println("oddInts.min() is ${oddInts.min()}") val prod = oddInts.reduce{x, y -> x * y} println("The product of $oddInts is $prod") } ``` + [`any`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/any.html) and [`all`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/all.html) ```kotlin fun main() { val listInt = listOf(1, 2, 3, 3) if (listInt.any{it % 2 == 0}) println("$listInt contains an even number.") if (listInt.all{it % 2 == 0}) println("Elements in $listInt are all even.") val x = 3 if (listInt.any{it == x}) println("$listInt contains $x.") val y = listOf(1, 2, 2, 3) if (y.all{listInt.contains(it)}) println("$listInt contains all elements in $y.") } ``` + Mutable version: `MutableList` -- [reference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-list/) + `mutableListOf` + `.toMutableList` and `.toList` + `add` + `removeAt` vs `remove` + `set` vs index ```kotlin fun main() { val mList = mutableListOf(1, 2, 3, 2, 3, 2, 5) mList.add(-9) println(mList) mList.add(5,566) println(mList) mList.removeAt(4) println(mList) mList.remove(2) println(mList) mList.set(2, 7) // mList[2] = 7 println(mList) } ``` + Shuffle and sort ```kotlin fun main() { val mList = mutableListOf(1, 2, 3, 2, 7, 2, 5, -9) mList.shuffle() println(mList) mList.sort() println(mList) mList.sortBy{-it} println(mList) mList.sortWith(Comparator{x, y -> y * y - x * x}) println(mList) } ``` + Mutable vs immutable ```kotlin fun main() { var immutable = listOf(1, 2, 3) println(immutable) // You cannot reassign any element of a List // immutable[0] = 5 // But you can reassign the variable immutable immutable = listOf(5, 2, 3) println(immutable) val mutable = mutableListOf(1, 2, 3) println(mutable) // You can reassign any element of a MutableList mutable[0] = 5 // You cannot reassign the value mutable // mutable = mutableListOf(5, 2, 3) println(mutable) } ``` ### Practice #### Task 1 Help your instructor to adjust the scores. Transform the original score $x$ to $\left\lceil 10*\sqrt{x}\right\rceil$. + Input format: The input has only one line, and there are several original scores separated by spaces. Between any two adjacent scores, there is exactly one space. + Output format: Output the adjusted scores in the same format. + Sample input: ``` 49 70 90 ``` + Sample output: ``` 70 84 95 ``` #### Task 2: Sort words into the reversed alphabetical order while ignoring the cases. That is, both `Banana` and `cAT` should appear before `aPpLe`. + There are several words separated by spaces, and there is exactly one space between adjacent words. + All words are different. + Sample input: ``` ApPlE cAT Banana ``` + Sample output: ``` cAT Banana ApPlE ``` #### Task 3 Read an arithmetic expression and find the index of the last evaluated operator. + Operators includes `+`, `-`, `*`, `/` + The brackets `(` and `)` are properly balanced. + All operands are non-negative decimal numbers. + No two numbers are adjacent. + You may assume that there is always a space between any pair of operators, between an operator and an operand, and between a bracket and any operator/operand. + Valid inputs: + `( 1 + 2 ) * 3 * ( 4 + 5 )` + Invalid inputs: + `1 2 + 3` + `1` and `2` cannot be adjacent + `(1+2)*3` + Lack of proper spaces + `1++2` + Not an expression + `2(3)` + Not an expression + You may output anything if the expression is invalid. + Sample input ``` ( 1 + 2 ) * 3 * ( 4 + 5 ) ``` + Sample output ``` 14 ``` ## Set + Collection of unique elements + No repeated values + Elements have the same type + `Set<Int>` + `Set<Double>` + `Set<String>` + The order of elements has no significance in general. + But the default implementation `LinkedHashSet` stores the insertion order of elements. + Much faster `.contains()` ```kotlin fun main() { val numbers = setOf(1, 2, 3) val revNumbers = setOf(3, 2, 1) val mutableNumbers = mutableSetOf(2, 1, 3) println("numbers: $numbers") println("revNumbers: $revNumbers") println("mutableNumbers: $mutableNumbers") println("Are numbers and revNumbers equal? ${numbers == revNumbers}") println("Are numbers and mutableNumbers equal? ${numbers == mutableNumbers}") } ``` ### Faster `.contains` and `in` operator ```kotlin fun main() { val numbers = setOf(1, 2, 3) println("numbers: $numbers") println("numbers contains 5: ${numbers.contains(5)}") println("numbers contains 5: ${5 in numbers}") println("numbers does not contain 5: ${!numbers.contains(5)}") println("numbers does not contain 5: ${5 !in numbers}") } ``` ### Add and subtract We can change a `MutableSet` by adding element to it and removing elements from it. + Adding: use `+` or `+=` + `+` does not change the `MutableSet` but create a new instance of `Set` or `MutableSet`. + `+=` changes the `MutableSet` + Removing: use `-` or `-=` ```kotlin fun main() { val numbers = mutableSetOf(1, 2, 3) val ref = numbers // Use + will not change numbers, but create a new instance (numbers + 4).also { println("numbers + 4 = $it: ${it is MutableSet<Int>}") } println(numbers) // Use += will change numbers numbers += 4 println(numbers) println(ref) // Note: numbers is a val not a var. // numbers = numbers + 5 // println(numbers) numbers -= 2 println(numbers) numbers += listOf(5, 6) println(numbers) numbers -= setOf(4, 3, 9) println(numbers) } ``` #### `union` and `intersect` Both of them are infix function ```kotlin fun main() { val numbers = setOf(1, 2, 3, 4, 5) val evenNumbers = setOf(0, 2, 4, 6, 8) println(numbers union evenNumbers) println(numbers.union(evenNumbers)) println(numbers intersect evenNumbers) println(numbers.intersect(evenNumbers)) println("numbers: $numbers") println("evenNumbers: $evenNumbers") } ``` If you want to change the `MutableSet`, please consider the following usage. ```kotlin fun main() { val numbers = mutableSetOf(1, 2, 3, 4, 5) // union and change numbers += setOf(0,2,4,6,8) println(numbers) // intersect and change numbers.removeIf{it !in setOf(1,3,5,7,9)} println(numbers) } ``` ## Map + A set of key-value pairs + Keys are unique + `.keys` + Values can be repeated + `.values` + Can be considered as a more general List + Values are indexed by key ```kotlin fun main() { val numMap = mapOf("one" to 1, "two" to 2, "zero" to 0) println("All keys: ${numMap.keys}") println("All values: ${numMap.values}") // in operator checks key, not key-value pair. // Equivalent to .contains(key) if ("one" in numMap) println("Value by key \"one\": ${numMap["one"]}") if (1 in numMap.values) println("The value 1 is in the map") // .containsValue works like .values.contains if (numMap.containsValue(1)) println("The value 1 is in the map") // different to in operator // for-loop iterates key-value pairs for (item in numMap) println("$item has type ${item::class.qualifiedName}") for ((key, value) in numMap) println("key: $key, value: $value.") } ``` + Equality: Check if all key-value pairs are equal regardless their order. ```kotlin fun main() { val numMap = mapOf("one" to 1, "two" to 2, "zero" to 0) val another = mapOf("zero" to 0, "one" to 1, "two" to 2) println("numMap: $numMap") println("another: $another") println("numMap == another: ${numMap == another}") println("string representations are equal: ${"$numMap" == "$another"}") } ``` ### Get methods + `.get(key)` and `[key]` return `null` if `.keys` does not contain `key`. + Similar to `List.getOrNull` + `.getValue(key)` throws an `Exception` if `.keys` does not contain `key`. + Similar to `List.get` + `getOrElse(key){expression}` returns the value of expression if `.keys` does not contain `key`. + `getOrDefault(key,defaultValue)` returns `defaultValue` if `.keys` does not contain `key`. ```kotlin fun main() { val numMap = mapOf("one" to 1, "two" to 2, "zero" to 0) println("numMap.get(\"one\"): ${numMap.get("one")}") println("numMap[\"one\"]: ${numMap["one"]}") println("numMap.getValue(\"one\"): ${numMap.getValue("one")}") println("numMap.get(\"TWO\"): ${numMap.get("TWO")}") println("numMap[\"TWO\"]: ${numMap["TWO"]}") // CRASH!! // println("numMap.getValue(\"TWO\"): ${numMap.getValue("TWO")}") println("numMap.getOrElse(\"TWO\"){222}: ${numMap.getOrElse("TWO") { 222 }}") println("numMap.getOrDefault(\"TWO\",333): ${numMap.getOrDefault("TWO", 333)}") // It is also ok to use ?: operator println("numMap[\"TWO\"]?:444 : ${numMap["TWO"] ?: 444}") } ``` ### `MutableMap` #### Put Methods + `.put(key, value)` + `[key] = value` + `.getOrPut(key,value)` + `.putAll(pairs)` ```kotlin fun main() { val map = mutableMapOf<String, Int>() map.put("zero", 0) println(map) map["one"] = 0 println(map) map["one"] = 1 println(map) // getOrPut is get if the key is in the map println(map["one"]) println(map.getOrPut("one") { 2 }) println(map["one"]) // getOrPut is put if the key is not in the map println(map["two"]) println(map.getOrPut("two") { 2 }) println(map["two"]) // putAll changes the existed key-value pairs map.putAll(mapOf("two" to -2, "three" to 3)) println(map) // putAll accepts list of pairs map.putAll(listOf(Pair("four", 4), Pair("five", 5))) println(map) // to is the infix version of Pair map.putAll(listOf("Six" to 6, "Ten" to 10)) println(map) } ``` #### Remove methods + `remove(key)` + Returns `null` if the `MutableMap` does not contain `key`. + Otherwise, returns the corresponding value. + `remove(key,value)` return `Boolean` + If `.get(key) == value`, remove the key-value pair and returns true. + Otherwise returns false. ```kotlin fun main() { val map = mutableMapOf("a" to 1, "b" to 2, "c" to 3, "d" to 4) // remove a key in the map println(map.remove("a")) println(map) // remove a key not in the map println(map.remove("z")) println(map) // remove a key-value pair in the map println(map.remove("b", 2)) println(map) // remove a key-value pair not in the map println(map.remove("c", -1)) println(map) println(map.remove("D", 4)) println(map) } ``` #### `plus` and `minus` Operation + Works for both immutable and mutable maps. + Create new `Map` instances. + A map `+` an element: create a new `Map` that contains all elements in the map and the element. + `+` on two maps: create a new `Map` that contains all elements in both sides. + A map `-` a key: create a new `Map` that contains all elements in the map but the element. + A map `-` a list of keys: create a new `Map` that contains the elements in the left hand side but the keys in the right hand side. ```kotlin fun main() { val map = mapOf("a" to 1, "b" to 2, "c" to 3) println(map + Pair("d", 4)) println(map + Pair("e", 5)) println(map + ("z" to 26)) println(map + mapOf("f" to 6, "g" to 7)) println(map + listOf("c" to -3, "y" to 25)) // map remains unchanged println(map) println(map - "a") println(map - "b") println(map - listOf("a", "c")) println(map - listOf("b", "d")) // map remains unchanged println(map) } ``` #### `plusAssign` and `minusAssign` Operatons + `+=` a key-value pair: `.put(key,value)` + `+=` a collection of key-value pairs: `.putAll(pairs)` + `-=` a key: `.remove(key)` + `-=` a collection of keys: `for (key in keys)` + `.remove(key)` ```kotlin fun main() { val map = mutableMapOf<String, Int>() map += "a" to 1 println(map) map += Pair("b", 2) println(map) map += mapOf("c" to 3, "d" to 4) println(map) map += listOf("e" to 5, "z" to 26) println(map) map -= "a" println(map) map -= "A" println(map) map -= listOf("e", "f", "z") println(map) } ``` #### Iterating Items and Destructuring + Iterate over map: Entry + `.key` + `.value` + `.filterKeys`, `.mapKeys`, ... + `.filterValues`, `.mapValues`, ... ```kotlin fun main() { val map = mapOf("a" to 1, "b" to 2, "c" to 3) // Entry have .key and .value println(map.filter { it.key < "b" || it.value > 2 }) // Destructuring Entry into (key, value) println(map.filter { (key, value) -> key < "b" || value > 2 }) // filter with key println(map.filterKeys { it < "b" }) // filter with value println(map.filterValues { it > 2 }) } ``` ### Practice for `Map` #### Task 1 Write a function `fun mostFrequent(str: String): Char?` + It returns `null` if `str` is `""`. + Otherwise, it returns the `Char` that has most occurences in `str`. If there are multiple candidates, output the smallest one in alphabetic order. #### Task 2 Write a function `fun wordCount(str: String): Map<String,Int>` + It returns a `Map`. + `str` contains only spaces and English alphabets. + Keys are words in lowercases. + Values are the number of occurences of the correspoinding keys in `str`. #### Task 3 Write a function `fun printMap(map: Map<String,Int>): Unit` that print the key-value pairs of `map` in dictionary order (case insentitive).