Book Image

Kotlin Standard Library Cookbook

By : Samuel Urbanowicz
Book Image

Kotlin Standard Library Cookbook

By: Samuel Urbanowicz

Overview of this book

For developers who prefer a more simplistic approach to coding, Kotlin has emerged as a valuable solution for effective software development. The Kotlin standard library provides vital tools that make day-to-day Kotlin programming easier. This library features core attributes of the language, such as algorithmic problems, design patterns, data processing, and working with files and data streams. With a recipe-based approach, this book features coding solutions that you can readily execute. Through the book, you’ll encounter a variety of interesting topics related to data processing, I/O operations, and collections transformation. You’ll get started by exploring the most effective design patterns in Kotlin and understand how coroutines add new features to JavaScript. As you progress, you'll learn how to implement clean, reusable functions and scalable interfaces containing default implementations. Toward the concluding chapters, you’ll discover recipes on functional programming concepts, such as lambdas, monads, functors, and Kotlin scoping functions, which will help you tackle a range of real-life coding problems. By the end of this book, you'll be equipped with the expertise you need to address a range of challenges that Kotlin developers face by implementing easy-to-follow solutions.
Table of Contents (11 chapters)

Discovering the concept of sequences

In terms of high-level functionalities, the Sequence and Collection data structures are nearly the same. They both allow iteration through their elements. There are also many powerful extension functions in the Kotlin standard library that provide declarative-style data-processing operations for each of them. However, the Sequence data structure behaves differently under the hood—it delays any operations on its elements until they are finally consumed. It instantiates the subsequent elements on the go while traversing through them. These characteristics of Sequence, called lazy evaluation, make this data structure quite similar to the Java concept, Stream. To understand all of this better, we are going to implement a simple data-processing scenario to analyze the efficiency and behavior of Sequence and contrast our findings with Collection-based implementation.

Getting ready

Let's consider the following example:

val collection = listOf("a", "b", "c", "d", "e", "f", "g", "h")
val transformedCollection = collection.map {
println("Applying map function for $it")
it
}
println(transformedCollection.take(2))

In the first line, we created a list of strings and assigned it to the collection variable. Next, we are applying the map() function to the list. Mapping operation allows us to transform each element of the collection and return a new value instead of the original one. In our case, we are using it just to observe that map() was invoked by printing the message to the console. Finally, we want to filter our collection to contain only the first two elements using the take() function and print the content of the list to the console.

In the end, the preceding code prints the following output:

Applying map function for a
Applying map function for b
Applying map function for c
Applying map function for d
Applying map function for e
Applying map function for f
Applying map function for g
Applying map function for h
[a, b]

As you can see, the map() function was properly applied to every element of the collection and the take() function has properly filtered the elements of the list. However, it would not be an optimal implementation if we were working with a larger dataset. Preferably, we would like to wait with the execution of the data-processing operations until we know what specific elements of the dataset we really need, and then apply those operations only to those elements. It turns out that we can easily optimize our scenario using the Sequence data structure. Let's explore how to do it in the next section.

How to do it...

  1. Declare a Sequence instance for the given elements:
val sequence = sequenceOf("a", "b", "c", "d", "e", "f", "g", "h")
  1. Apply the mapping operation to the elements of the sequence:
val sequence = sequenceOf("a", "b", "c", "d", "e", "f", "g", "h")
val transformedSequence = sequence.map {
println("Applying map function for $it")

it
}
  1. Print the first two elements of the sequence to the console:
val sequence = sequenceOf("a", "b", "c", "d", "e", "f", "g", "h")

val transformedSequence = sequence.map {
println("Applying map function for $it")
it
}
println(transformedSequence.take(2).toList())

How it works...

The Sequence-based implementation is going to give us the following output:

Applying map function for a
Applying map function for b
[a, b]

As you can see, replacing the Collection data structure with the Sequence type allows us to gain the desired optimization.

The scenario considered in this recipe was implemented identically—first, using List, then using the Sequence type. However, we can notice the difference in the behavior of the Sequence data structure compared to that of Collection. The map() function was applied only to the first two elements of the sequence, even though the take() function was called after the mapping transformation declaration. It's also worth noting that in the example using Collection, the mapping was performed instantly when the map() function was invoked. In the case of Sequence, mapping was performed at the time of the evaluation of its elements while printing them to the console, and more precisely while converting Sequence to the List type with the following line of code:

println(transformedSequence.take(2).toList())

There's more...

There is a convenient way of transforming Collection to Sequence. We can do so with the asSequence() extension function provided by the Kotlin standard library for the Iterable type. In order to convert a Sequence instance into a Collection instance, you can use the toList() function.

See also

  • Thanks to the feature of Sequence lazy evaluation, we have avoided needless calculations, increasing the performance of the code at the same time. Lazy evaluation allows the implementation of sequences with a potentially infinite number of elements and turns out to be effective when implementing algorithms as well.

  • You can explore a Sequence-based implementation of the Fibonacci algorithm in the Applying sequences to solve algorithmic problems recipe. It presents, in more detail, another useful function for defining sequences called generateSequence().