The Programmers Guide To Kotlin- Iterators & Sequences
Written by Mike James   
Wednesday, 24 August 2022
Article Index
The Programmers Guide To Kotlin- Iterators & Sequences
Ranges & Progressions

Iterators and sequences are a large part of using collections and taking a functional approach to programming. We look at how they work in Kotlin.

Programmer's Guide To Kotlin Second Edition

kotlin2e360

You can buy it from: Amazon

Contents

  1. What makes Kotlin Special
  2. The Basics:Variables,Primitive Types and Functions 
  3. Control
         Extract: If and When 
  4. Strings and Arrays
  5. The Class & The Object
  6. Inheritance
  7. The Type Hierarchy
  8. Generics
  9. Collections, Iterators, Sequences & Ranges
        Extract: Iterators & Sequences 
  10. Advanced functions 
  11. Anonymous, Lamdas & Inline Functions
  12. Data classes, enums and destructuring
        Extract: Destructuring 
  13. Exceptions, Annotations & Reflection
  14. Coroutines
        Extract: Coroutines 
  15. Working with Java
        Extract: Using Swing ***NEW!

<ASIN:B096MZY7JM>

It is assumed that you know the basics of collections and in particular how Kotlin presents collections to the user. This is covered in detail in the first part of this chapter. Here we are only concerned with iterators and the related idea of a sequence.

In chapter but not in this extract

  • Collection Basics
  • List & MutableList
  • The Collections
  • Map
  • Set

Iterators

Collections of objects usually have an iterator to allow you to step through each object in turn. In fact, you can have an iterator that isn't associated with a collection of objects and simply generates the next object on demand.

An iterator has to have at least two functions, next which returns the next object and hasNext which returns true if there is a next object. This means the Kotlin for can be written as:

while(it.hasNext()){
e = it.Next()
instructions that use e
}

For example:

var a = mutableListOf<String>("a","b","c")
for(e in a.iterator()) println(e) var it=a.iterator()
while(it.hasNext()){
var e=it.next()
println(e)
}

The two loops work in the same way. The iterator method returns an initialized iterator for the collection. You can use this to retrieve each element in turn. Notice that you can't reset an iterator to the first value – you simply create a new instance. If this behavior doesn't suit you then simply include a start and/or end parameter in the constructor and modify the Next and hasNext methods accordingly.

Although iterators are generally linked to collection style data structures, they can also be used to generate sequences. In the case of a collection, the Next method retrieves the next item in the collection, but for a sequence it simply computes the next value. For example a CountToTen class would be something like:

class CountToTen():Iterator<Int>{
private var i:Int=1
override fun next(): Int {
return i++
} override fun hasNext(): Boolean {
if(i>10) return false
return true
}
}

and it could be used anywhere you needed the sequence of numbers. For example:

val a=CountToTen()
for(e in a) println(e)

prints 123 ... 10.

In most cases it would be better to create a class that implemented the Iterable interface. This has just one operator method, iterator, which returns an Iterator object for the class in question. Notice that an iterator is "lazy" in the sense that it doesn't compute the complete sequence of values at once, it only computes a value when needed.

Kotlin has a lot of facilities for functional programming and in functional programming you often chain together functions like iterators which produce sequences. For efficiency, it is important that these put off creating a sequence until it is actually needed – i.e. they need to be lazy in an extended sense. Kotlin provides the Sequence<T> type to allow you to use and implement iterators that are lazy in this broader sense.

Sequences

Although functional programming isn't the focus of this book, it is worth giving an example of the way iterators and sequences differ, if only because non-functional programmers find it hard to understand why there are two such closely related types. Compare this code which uses a sequence:

val s = sequenceOf(1,2,3,4,5,6,7,8,9,10)
val ms = s.map({println(it);it})
println("end")

with this using the iterator associated with the List:

val i = listOf(1,2,3,4,5,6,7,8,9,10)
val mi = i.map({println(it);it})
println("end")

The map method simply applies the specified function, i.e. the println, to each of the elements of the sequence or collection. If you run this you will discover that the map acting on the sequence doesn't print anything, but the List iterator does. The reason is that in the case of the sequence, map returns an unevaluated sequence ready for further operations. In the case of the List, the map returns another List after evaluating everything. If you want to force the sequence to evaluate, you have to do something that makes use of its results, for example:

val s = sequenceOf( 1,2,3,4,5,6,7,8,9,10 )
val ms = s.map({println(it);it})
println("end")
println(ms.last())

You will now see printed:

end
1
2
3
4
5
6
7
8
9
10
10

This unexpected order of events, i.e. the map happens after the “end” happens because the map isn’t evaluated until it is actually needed. So the assignment to ms doesn’t evaluate the map, it is simply stored ready to be evaluated when needed. Then “end” is printed and then the map is finally evaluated as its last element is needed. The evaluation is even more lazy than you might imagine in that that if you use first in place of last then only the first element is evaluated. That is all you see is:

end
1
1

The lazy evaluation is only performed to the point where the element that is actually needed becomes available.

This is aggressively lazy and it has to be to ensure that many functional forms of simple algorithms are practical.



Last Updated ( Wednesday, 24 August 2022 )