The Programmers Guide To Kotlin - Iterators, Sequences & Ranges |
Written by Mike James | |||
Monday, 29 January 2018 | |||
Page 1 of 2 Collections are fundamental to the use of most object-oriented languages and Kotlin is no different. It takes the Java collections and pushes them further. In this extract from my recently published book, the focus is on iterators, sequences and ranges. IteratorsCollections 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. For example the Kotlin for can be written as:
For example:
The two for 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:
and it could be used anywhere you needed the sequence of numbers. For example:
prints 1 to 10. In most cases it would be better to create a class that implemented the Iterable interface. This has just one 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. SequencesAlthough 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 so closely related types. Compare:
which uses a sequence and:
which uses the iterator associated with the List. 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 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:
You will now see printed:
This happens as the sequence is evaluated to get the last element which is then printed. Notice that if you use first in place of last then only the first element is evaluated. This is aggressively lazy and it has to be to ensure that many functional forms of simple algorithms are practical. Ranges & ProgressionsKotlin provides two data types that make it easier to write for loops – the Range and the Progression. Roughly speaking, the Range allows you to write things like:
and a Progression is a Range for which you can specify a step size. For example:
As already explained in connection with the for loop, Ranges and Progressions integrate into the for using the rangeTo function as an operator .. and the step function. It isn't difficult to create your own Ranges and Progression. As an example let's create a Range that works for dates. In Java there are far too many Date classes in use at the moment, so for simplicity we will use the original Date class despite most of its methods being deprecated. To use this in a Kotlin project you need to add:
To create a date range class we need to implement a class, DateRange, that implements the Iterable interface, and ClosedRange interface which has the endInclusive property:
We need to add the override modifier to the constructor because the properties created by the primary constructor are hiding properties inherited from ClosedRange. We also have to implement iterator which returns a suitable Iterator for the range:
where DateRangeIterator is the iterator we have yet to implement. DateRangeIterator inherits from Iterator and ClosedRange. We only need to override the Iterator's next and hasNext methods and provide implementations of the ClosedRange start and endInclusive properties:
Notice that as Date is an object we can't just use:
because next and current would both reference the same object and changes made to current would also change next. To keep the values separate we have to create a new instance with the same values – i.e. we have to clone current. To make the .. operator work we also need to define a suitable rangeTo function:
With this all defined, all that remains is to use the new Range class:
which prints:
<ASIN:1871962536> <ASIN:1871962544> |
|||
Last Updated ( Monday, 29 January 2018 ) |