Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sequences - Kotlin

Sequences - Kotlin

Sequences are of container type, just as collections. They only differ in in how they handle multi-step processing. In a chained operations call collections are evaluated eagerly. This way intermediate collection are returned when each step completes processing. This creates overhead. Sequences solves this by evaluating chained operations lazily: actual computing happens only when the result of the whole processing chain is requested.

Beatrice Kinya

September 28, 2019
Tweet

More Decks by Beatrice Kinya

Other Decks in Programming

Transcript

  1. Recap Collections - Groups of a variable number of items,

    probably zero. Kotlin collections comprises of: ❖ List - ordered collection with access to elements by indices ❖ Set - collection of unique elements ❖ Map/dictionary - set of key-value pairs All extension functions that allow us to work with collections in a functional style are defined as inline functions. These functions return a collection.
  2. How many collections are created while running the code below?

    val list = listOf(1, 2, 3) val maxOddSquare = list .map { it * it } .filter { it % 2 == 1 } .max()
  3. Introduction to sequences When you use simple operations on collection,

    on a chained calls, intermediate collections are created. This creates a performance overhead due to the intermediate collections created. Sequences solve this problem. Sequences are a container type, just as collections. Sequences offer the same functions as Iterable but implement another approach to multi-step collection processing.
  4. Converting a collection to a sequence. Call asSequence() from the

    collection val fruits = listOf("Apple", "Mango", "Lemon", "Orange", "Grapes", "Berries") val fruitsSequence = fruits.asSequence()
  5. Collections vs Sequences Collections are evaluated eagerly. When processing includes

    multiple steps, each processing step completes and returns a result, an intermediate collections. Sequences are evaluated lazily, i.e. postponing computation hence avoiding creating intermediate collections. The postponed computation is performed only when the result of the processing is required. Horizontal and vertical evaluation can also be used to explain these concepts
  6. Horizontal vs Vertical evaluation val list = listOf(1, 2, 3,

    4) val list = listOf(1, 2, 3, 4) val number = list val numberSequence = list .map { it * it } .asSequence() .find { it >3 } .map { it * it } .find { it > 3 }
  7. Horizontal vs Vertical evaluation cont’ .map { it * it

    } .map { it * it } 1 4 9 16 1 4 .find { it > 3} .find { it > 3} 1 4 1 4
  8. Creating a sequence From elements val animalSequence = sequenceOf("Lion", "Antelope",

    "Zebra") println(animalSequence.toList()) From iterable val numbers = listOf(1, 2, 3, 4, 5, 7, 9) val numberSequence = numbers.asSequence() println(numberSequence.toList())
  9. Creating a sequence cont’ From a function Creating a sequence

    is by building it with a function that calculates its elements, generateSequence() val oddNumbers = generateSequence(1) { it +2 } println(oddNumbers.take(5).toList()) //println(oddNumbers.count()) generates infinite no. of integers .Therefore this method throws an exception or runs infinitely
  10. Creating a sequence cont’ From a function. To create a

    finite sequence provide a function that returns null after the last element you need //create a sequence of even numbers upto 20 val evenNumbers = generateSequence(0) { if ( it < 20) it +2 else null} println(evenNumbers.toList())
  11. Creating a sequence cont’ From chunks sequence() function allows you

    to produce sequence elements either one by one or by chunks of arbitrary size. This function takes a lambda expression containing calls of yield() and yieldAll() functions. They return an element to the sequence consumer and suspend execution of sequence until the next element is requested by the consumer.
  12. Creating a sequence cont’ From chunks yield() takes a single

    element as an argument. yieldAll() takes an iterable object, an iterator, or another sequence. A sequence argument of yieldAll() can be infinite. However such should be called last. Otherwise all other calls will never be be executed.
  13. Creating sequences cont’ val oddNumbers = sequence { yield(1) yieldAll(listOf(3,

    7, 11)) yieldAll(generateSequence(7) { it + 2 }) //yieldAll() taking an infinite argument }
  14. Exercise Fibonacci sequence Implement the function that builds a sequence

    of Fibonacci numbers using 'sequence' function. hint: use 'yield'. Solution Fun main(){ //print the first 10 elements of the fibonacci series println(fibonacci().take(10).toList())
  15. Solution fun fibonacci(): Sequence<Int> = sequence { var elements =

    Pair(0, 1) //create the fibonacci series while (true){ yield(elements.first) elements = Pair(elements.second, elements.first + elements.second) }
  16. Sequence operations. Intermediate operations - return a sequence which is

    produced lazily e.g. map(), filter() etc. etc Terminal operations - Terminal operations evaluate sequences e.g. toList(), sum(). Terminal operations are used to retrieve elements in a sequence. Stateless operations - These operations do not require knowledge of the sequence. They process each element independently This allows for vertical processing of a sequence, e.g map() or filter() Stateful operations - These require knowledge of the sequence before it is evaluated, e.g, toList(), or sorted() . This property forces horizontal processing.