Search

Dark theme | Light theme

December 11, 2022

Kotlin Kandy: Transform Items In A Collection To A Map With associate

Kotlin gives us the associate method for collection objects, like lists, iterables and arrays. With this method we can convert the items in the collection to a new Map instance. The associate method accepts a lambda function as argument and we must return a Pair from the lambda. The first item of the pair will be the key and the second element is the value of the key/value pair in the resulting map.

If we want to use the elements in our collection as key, but want to transform the value we must use associateWith. The lambda for this method must return the value part of our key/value pair. Alternatively if we only want to transform the key value we can use associateBy with one lambda function. The lambda function must return the result for the key in the key/value pair of the map. The method associateBy is overloaded where we can pass two lambda functions. The first lambda function is for transforming the key and the second lambda function is for transforming the value.

In the following example we use all variants of the associate methods:

// Sample list of string vlaues.
val languages = listOf("Kotlin", "Groovy", "Java", "Clojure")

// With associate we use a lambda to return a Pair.
// The Pair is the key/value for the resulting map.
// In this example we use the item in lowercase as key and
// the length of the item as value.
assert(languages.associate { s -> s.lowercase() to s.length } ==
         mapOf("kotlin" to 6, "groovy" to 6,
               "java" to 4, "clojure" to 7))

// associateBy accepts a lambda to return the key value
// of a pair and the value is the element from the collection.
// Here we want to use the lowercase value of the item as key.
assert(languages.associateBy(String::lowercase) ==
         mapOf("kotlin" to "Kotlin", "groovy" to "Groovy",
               "java" to "Java", "clojure" to "Clojure"))

// associateBy accepts a second lambda to also transform
// the value part of the key/value pair.
// With a second lambda we transform the item to it's length as value.
assert(languages.associateBy(String::lowercase, String::length) ==
         mapOf("kotlin" to 6, "groovy" to 6,
                "java" to 4, "clojure" to 7))

// associateWith accepts a lambda to transform the value part
// of the key/value pair. The key is then the element
// from the collection.
// We use the item of the collectio as key, but use the
// length of the string value as value for our key.
assert(languages.associateWith(String::length) ==
         mapOf("Kotlin" to 6, "Groovy" to 6,
                "Java" to 4, "Clojure" to 7))

Each of the assiocate methods return a Map with key/value pairs. If we want to add the result to an existing, mutable Map instance we must use the methods associateTo, associateByTo and associateWithTo. The first argument is a mutable Map and the rest of the arguments is the same as for the associate methods without To.

In the following example we want to add new key/value pairs to an existing Map using associate method variants:

// Example list of some numbers.
val numbers = listOf(2, 3, 4)

// Helper function to return the square value of a given number.
fun square(n: Int) = n * n

// For each of the associate/associateBy/associateWith methods there is
// an equivalent associateTo/associateByTo/associateWithTo to add the result
// to an existing mutable map instance.
assert(numbers.associateTo(mutableMapOf(0 to 0, 1 to 1)) { n -> n to square(n) } ==
         mapOf(0 to 0, 1 to 1, 2 to 4, 3 to 9, 4 to 16))
         
assert(numbers.associateByTo(mutableMapOf(0 to 0, 1 to 1)) { n -> n * 10 } ==
        mapOf(0 to 0, 1 to 1, 20 to 2, 30 to 3, 40 to 4))

assert(numbers.associateByTo(mutableMapOf(0 to 0, 1 to 1), { n -> n }, ::square) ==
         mapOf(0 to 0, 1 to 1, 2 to 4, 3 to 9, 4 to 16))

assert(numbers.associateWithTo(mutableMapOf(0 to 0, 1 to 1), ::square) ==
         mapOf(0 to 0, 1 to 1, 2 to 4, 3 to 9, 4 to 16))

Written with Kotlin 1.7.20.