9 minutes read

In the previous topics, you have seen different ways of working with collections: filtering, grouping, mapping, and ordering.

It is time to go one step further in transformation operations and talk about more complex operations, which will give us more power when dealing with collections and their elements.

Zip

Zipping is an operation that allows us to obtain a pair based on two elements that occupy the same position in two different collections. This way, you can get a collection of pairs after applying zip().

If the original collections do not have the same size, the result will be a list of pairs limited to the size of the smaller collection, ignoring the remaining elements of the larger one.

You can also call zip() with a transformation function. The resulting List will contain the return values of the transformation function called on pairs of the receiver and the argument elements with the same positions.

Unzipping helps us to perform the reverse transformation. Given a collection of pairs, you can transform it into two collections with unzip(). The first one will have the first elements of the pair, the second one – the second elements.

fun main() {
    val listNumbers = listOf(1, 2, 3, 4, 5)
    val listStrings = listOf("one", "two", "three", "four", "five")

    // Zipping
    val zipped = listNumbers.zip(listStrings)
    println(zipped) // [(1, one), (2, two), (3, three), (4, four), (5, five)]

    // Unzipping
    val unzipped = zipped.unzip()
    println(unzipped) // ([1, 2, 3, 4, 5], [one, two, three, four, five])

    // Zipping with different length
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    // other way to call zip function
    val zippedWithDifferentLength = numbers.zip(listStrings)
    // ignore the rest of numbers
    println(zippedWithDifferentLength) // [(1, one), (2, two), (3, three), (4, four), (5, five)]

    // Zipping with transform
    val zippedWithTransform = numbers.zip(listStrings) { number, string ->
        "$number - $string"
    }
    println(zippedWithTransform) // [1 - one, 2 - two, 3 - three, 4 - four, 5 - five]
}

Associate

Association allows us to create maps from collections of elements and the values associated with them. These elements can be keys or values in the resulting map.

The associateWith() method allows us to obtain a map where the elements are the keys. The map values are obtained with the help of a transformation function. If two items are the same, only one of them will be on the Map.

The associateBy() method offers us a way to create a map where the elements of a collection are the values. The key is obtained with the help of a transformation function. If two items have the same key, only one of them will be on the Map.

You can also obtain maps where the key or value is obtained from the elements of a collection thanks to the associate() function. This function takes a lambda that returns a Pair that corresponds to the map entry. It should be used with care – when performance is not critical and when you cannot make use of the other options.

fun main() {

    val listStrings = listOf("one", "two", "three", "four", "five")

    // Associate With
    val associatedWith = listStrings.associateWith { it.length }
    println(associatedWith) // {one=3, two=3, three=5, four=4, five=4}

    // Associate By
    val associatedBy = listStrings.associateBy { it.first().uppercase() }
    println(associatedBy) // {O=one, T=three, F=five}

    // Associate with a lambda
    val associatedWithLambda = listStrings.associate {
        it.first().uppercase() to it.length
    }
    println(associatedWithLambda) // {O=3, T=5, F=4}
}

String representation

One of the problems you face when working with collections is that they can't be easily read. Thanks to these methods, you can represent collections in a readable format.

The joinToString() method builds the string representation when you call toString() based on the elements of a collection using the following arguments:

  • separator: the separator between the elements, for example, ";".

  • prefix: represents the beginning of the collection, for example, "{".

  • postfix: represents the ending of the collection, for example, "}".

  • limit: the number of elements to be included in the string representation, for example, 3.

  • truncated: when you use a limit, the rest of elements will be represented with this value, for example, "..."

These parameters are not required. If they are not passed, they will have the default value.

The joinTo() methods performs the same task – appends the result to the given Appendable object (an object to which char sequences and values can be appended, for example, StringBuilder).

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    // joinToString
    val joinToString = numbers.joinToString(
        separator = " ; ",
        prefix = "{ ",
        postfix = " }",
        limit = 3,
        truncated = "..."
    )
    println(joinToString) // { 1 ; 2 ; 3; ... }
    
    // joinTo appendable
    val joinTo = numbers.joinTo(
        StringBuilder(),
        separator = " ; ",
        prefix = "{ ",
        postfix = " }",
        limit = 3,
        truncated = "..."
    )
    println(joinTo) // {1 ; 2 ; 3...}

}

Conclusion

In this topic, you have discovered more methods to perform transformations on our collections – either by creating a list of pairs based on two collections or creating maps where the keys and values are the elements of a collection. You've also learned how to improve the textual representation of a collection.

It's time to check everything you've learned about collection transformations and do some tasks. Are you ready?

57 learners liked this piece of theory. 1 didn't like it. What about you?
Report a typo