Kotlin Collections – Sequences

Using Sequences – the equivalent to Java Streams.

// When chaining a e.g. filter and map together there will be intermediate Collections formed in the background. What if there are a large number of chained actions or the Collection is very large (or unknown size)?
// To avoid the creation of intermediate Collections we can use Sequences. Equivalent to Streams in Java, and aren't available on all platforms.
// Each element is evaluated and passed to next step in chain (if appropriate). No need for intermediates
// Only one function - iterator. Can convert a Collection to a Sequence but ONLY DO THIS FOR LARGE COLLECTIONS as Kotlin Collections are very efficient.
 fun main() {
    val immutableMap = mapOf(1 to Car3("green", "Toyota", 2015),
        2 to Car3("red", "Ford", 2016),
        3 to Car3("silver", "Honda", 2013),
        17 to Car3("red", "BMW", 2015),
        8 to Car3("green", "Ford", 2010)
    )

    // Two types of Sequence - intermediate (returns a Sequence to pass onto next operation) and terminal (terminates the chain)
    println(immutableMap.asSequence().filter { it.value.model == "Ford" }
        .map {it.value.colour}) // Intermediate - lazy i.e. not evaluated immediately. If you never reach terminal operation would be wasteful to evaluate intermediates

    listOf("Joe", "Mary", "Jane").asSequence()
        .filter { println("Filtering $it"); it[0] == 'J' } // Intermediate operation
        .map { println("Mapping $it"); it.toUpperCase() } // Intermediate operation. No return - need terminal operation

    val name = listOf("Joe", "Mary", "Jane").asSequence()
        .filter { println("Filtering $it"); it[0] == 'J' }
        .map { println("Mapping $it"); it.toUpperCase() }
        .toList() // Terminal operation - can now see results of operations
    println(name)

    // In some cases the desired result will be returned long before iterating the entire dataset, so many intermediate Collections could be avoided by using Sequences:
    println("======")
    val name1 = listOf("Joe", "Mary", "Jane").asSequence()
        .filter { println("Filtering $it"); it[0] == 'J' }
        .map { println("Mapping $it"); it.toUpperCase() }
        .find { it.endsWith('E') } // Operation completes without ever having to evaluate Mary and Jane
    println(name1)
    println("======")

    // With Collections, the order in which you call functions makes a difference. If we reverse the filter and map functions:
    val name2 = listOf("Joe", "Mary", "Jane")
        .map { println("Mapping $it"); it.toUpperCase() }
        .filter { println("Filtering $it"); it[0] == 'J' }
        .find { it.endsWith('E') } // All 3 names are mapped and then filtered - less efficient
    println(name2)
    println("======")

    // But with Sequences, the order in which you call functions may or may not make a difference. If we reverse the filter and map functions:
    val name3 = listOf("Joe", "Mary", "Jane").asSequence()
        .map { println("Mapping $it"); it.toUpperCase() }
        .filter { println("Filtering $it"); it[0] == 'J' }
        .find { it.endsWith('E') } // No difference
    println(name3)
    println("======")

    // But if we put Joe last:
    val name4 = listOf("Mary", "Jane", "Joe").asSequence()
        .map { println("Mapping $it"); it.toUpperCase() }
        .filter { println("Filtering $it"); it[0] == 'J' }
        .find { it.endsWith('E') } // Much less efficient
    println(name4)

    // Moral of the story: think out the order in which you run operations
}

 

Kotlin Collections – Useful Functions

Useful functions for Kotlin Collections.

fun main() {
    val setInts = setOf(10, 15, 19, 5, 3, -22)

    println(setInts.filter { it % 2 != 0 }) // Odd numbers only

    val immutableMap = mapOf(1 to Car3("green", "Toyota", 2015),
        2 to Car3("red", "Ford", 2016),
        3 to Car3("silver", "Honda", 2013),
        17 to Car3("red", "BMW", 2015),
        8 to Car3("green", "Ford", 2010)
    )

    println("2016 cars only: ${immutableMap.filter { it.value.year == 2016 }}") // 2016 cars only

    val mutableMap = mutableMapOf(1 to Car3("green", "Toyota", 2015), 2 to Car3("red", "Ford", 2016), 3 to Car3("silver", "Honda", 2013))

    mutableMap.filter { it.value.colour == "silver" }

    println(mutableMap) // Map hasn't changed by being filtered. Filter creates new instance of the map.

    // Add 10 to every element and create new
    val ints = arrayOf(1, 2, 3, 4, 5)
    val add10List: MutableList<Int> = mutableListOf()
    for (i in ints) {
        add10List.add(i + 10)
    }
    println("Add10List: ${add10List}")
    // The Kotlin way - using the map FUNCTION (not Collection)
    val add10List2 = ints.map { it + 10 }
    println("Add10List2: ${add10List2}")

    // Create a List that contains all the years of the Cars in our Map
    val carYearsList = immutableMap.map { it.value.year }
    println("Car years list: ${carYearsList}")

    // You can chain filter and map functions together:

    println("Ford cars only: ${immutableMap.filter { it.value.model == "Ford" }}") // Ford cars only

    println("Ford car colours: ${immutableMap.filter { it.value.model == "Ford" }.map { it.value.colour}}") // Ford car colours

    // If all the elements in a Collection match a condition:
    println(immutableMap.all { it.value.year > 2014 }) // Are all cars modern (post-2015)?
    println(immutableMap.any { it.value.year > 2014 }) // Are any cars modern (post-2015)?
    println(immutableMap.count { it.value.year > 2014 }) // How many cars are modern (post-2015)?
    // To find value that matches we can move them into a List and use the find function
    val cars = immutableMap.values
    println("First modern car: ${cars.find { it.year > 2014}}") // Finds first item in List that satisfies condition

    println("Cars grouped by colour: ${cars.groupBy { it.colour }}") // Group cars by colour
    println("Cars sorted by year: ${cars.sortedBy { it.year }}") // Sorted by year

    println("Sorted by key: ${immutableMap.toSortedMap()}") // Sorted by key

}

data class Car3(val colour: String, val model: String, val year: Int)

 

Kotlin Collections – Maps

Using Maps in Kotlin.

fun main() {
    // Use 'to' to map the key to the value. Don't really need to state Type as is inferred by Kotlin
    val immutableMap = mapOf(1 to Car("green", "Toyota", 2015), 2 to Car("red", "Ford", 2016), 3 to Car("silver", "Honda", 2013))

    println(immutableMap.javaClass) // Returns java.util.LinkedHashMap
    println(immutableMap) // Returns keys and instance references as we haven't overwritten toString() or made a data class

    val mutableMap = mutableMapOf<String, Car>("John's car" to Car("red", "Range Rover", 2010), "Jane's car" to Car("blue", "Hyundai", 2012))
    println(mutableMap.javaClass) // Also returns java.util.LinkedHashMap. Kotlin prefers this as it has a predictable iteration order - allows easy conversion from List to Set to Map
    println(mutableMap)
    mutableMap.put("Mary's car", Car("red", "Corvette", 1965)) // Don't use the 'to' keyword when putting


    val mutableMap2 = hashMapOf<String, Car>("John's car" to Car("red", "Range Rover", 2010), "Jane's car" to Car("blue", "Hyundai", 2012)) // If you want a hashMap instead of a LinkedHashMap
    println(mutableMap2)
    mutableMap2.put("Mary's car", Car("red", "Corvette", 1965)) // Don't use the 'to' keyword when putting

    val pair = Pair(10, "ten") // Create a Pair
//    val firstValue = pair.first // Access 1st value
//    val secondValue = pair.second // Access 2nd value
    val (firstValue, secondValue) = pair // The Kotlin way - destructuring declaration. We've distributed the public values into different variables
    println(firstValue)
    println(secondValue)

    // If we want to iterate over the mutableMap:
    for ((k, v) in mutableMap) { // Destructuring declaration - only works in classes that have implemented component functions. To do this with custom class see next code block (Pair is a data class so has this included):
        println(k)
        println(v)
    }

    val car = Car("blue", "Corvette", 1959)
    val (colour, model, year) = car
    println("Colour: $colour Model: $model Year: $year")

    val car2 = Car2("blue", "Corvette", 1959)
    val (colour2, model2, year2) = car2
    println("Colour: $colour2 Model: $model2 Year: $year2")
}

class Car(val colour: String, val model: String, val year: Int) {

    operator fun component1() = colour // Implementing component functions. Only works if properties are public
    operator fun component2() = model
    operator fun component3() = year

}

data class Car2(val colour: String, val model: String, val year: Int) // No need to implement component functions as data classes already have them

 

Kotlin Collections – Lists

Using Lists in Kotlin.

// Kotlin often uses Java Collections, but even then you can do more with them
// Completely interoperable with Java
// You can operate on an immutable Collection (add, remove items), but the result will be a brand new instance
// All Collections take a generic type and all the read-only interfaces are covariant (e.g. you can assign a List of BigDecimal to a List of Any)
// If you look at the Collections declaration you will see it only has e.g. isEmpty() and contains(), and is covariant <out E>. MutableCollection adds add(), remove() etc and is not covariant <E>. Cannot assign mutable Collection of e.g. BigDecimal to Any.
// List and Set Collections. Array is considered a Collection but is in kotlin package, not kotlin/Collections. Doesn't implement any Collection interfaces.
fun main() {
    val strings = listOf("spring", "summer", "autumn", "winter") // This is wrong - produces List not ArrayList
    println(strings.javaClass) // returns java.utils.Arrays$ArrayList - can't add or remove anything but can change one of the elements (.set), therefore mutable.
    // Kotlin makes it immutable by not having any functions in the Kotlin List interface that can change the ArrayList
    // However, there are ways around this if the List is passed to Java code so need to be aware!

    val mutableSeasons = strings.toMutableList() // Creates a mutable List from an immmutable one
    mutableSeasons.add("Another season")
    println(mutableSeasons)

    val emptyList = emptyList<String>()
    println(emptyList.javaClass) // returns kotlin.collections.EmptyList. Not much you can do with this list so no need for Java class. Maybe you would return it where your function would usually return an immutable List.
//    println(emptyList[0]) // Still lets you call get() resulting in IndexOutOfBoundsException. Check if empty first if potentially receiving an empty List.

    val notnullList = listOfNotNull("hello", null, "goodbye") // You can use this technique to filter out any potential null results...
    println(notnullList) // ... returning a List of non-null values

    //We can get a standard Java ArrayList by specifically requesting it:
    val arrayList = arrayListOf(1, 2, 4)
    println(arrayList.javaClass) // Mutable List of class java.util.ArrayList

    val mutableList = mutableListOf<Int>(1, 2, 3)
    println(mutableList.javaClass) // Also mutable List of class java.util.ArrayList

    println(mutableList[2]) // We can use [] to get and set items
    mutableList[1] = 20
    println(mutableList)
    mutableList.set(0, 40) // Or we can use the getters and setters
    mutableList.get(2)

    val array = arrayOf("black", "white", "green")
    val colourlist = listOf(array) // Creates a List with one Array item
    println(colourlist) // We probably would have wanted an array of 3 elements, so:
    val actualColourList = listOf(*array)
    println(actualColourList)// or even easier...
    val actualColourList2 = array.toList()
    println(actualColourList2)

    val ints = intArrayOf(1, 2, 3)
    println(ints.toList()) // Converts a Kotlin Array to a primitive array for passing to Java

    // To manipulate Lists:
    println(strings.last()) // Get the last element
    println(strings.asReversed()) // Print them out backwards

    // The long way:
    if (strings.size > 5) {
        println(strings[5])
    }
    // The Kotlin way:
    println(strings.getOrNull(5)) // Will get element 5 or return null

    val ints1 = listOf(1, 2, 3, 4, 5)
    println(ints1.max()) // Gets largest value
    println(actualColourList.zip(strings)) // Only creates pairs for as long as pairs can be made

    val mergedLists = listOf(actualColourList, strings) // Creates a List of 2 Lists
    println(mergedLists)

    val combinedList = actualColourList + strings // Concatenates the 2 Lists
    println(combinedList)

    // To combine two lists and exclude duplicates:
    val strings2 = listOf("spring", "summer", "autumn", "summer", "winter")
    val colourList2 = listOf("black", "white", "red", "black", "red")

    val noDupsList = colourList2.union(strings2)
    println(noDupsList)

    // To remove duplicates without having to combine the List with anything else:
    val noDupColours = colourList2.distinct() // Returns a new list
    println(noDupColours)
}