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

Ranges & Progressions

Kotlin provides two data types that make it easier to write for loops – the Range and the Progression. Roughly speaking, Range allows you to write things like:

for(i in 1..10)

and a Progression is a range for which you can specify a step size, for example:

for(i in 1..10 step 2)

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 progressions. 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 rather than LocalDate, which is its successor. To use this in a Kotlin project you need to add:

import java.util.*

To create a date range class we need to implement a class, DateRange, that implements the Iterable interface, and the ClosedRange interface which has the endInclusive property:

class DateRange(override val start: Date,
   override val endInclusive:Date):Iterable<Date>,ClosedRange<Date>

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:

class DateRange(override val start: Date,
   override val endInclusive:Date):Iterable<Date>,
ClosedRange<Date>{
override fun iterator(): Iterator<Date> {
return DateRangeIterator(start,endInclusive)
}
}

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:

class DateRangeIterator(val start: Date, 
            val endInclusive: Date): Iterator<Date> {
private var current=start
override fun next(): Date {
var next = Date(current.getTime())
current.setTime(current.getTime() + 24*60*60*1000)
return next
} override fun hasNext(): Boolean {
if(current>endInclusive) return false
return true
}
}

Notice that as Date is an object we can't just use:

var next=current

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:

operator fun Date.rangeTo(other: Date) = 
DateRange(this, other)

With this all defined, all that remains is to use the new range class and it is worth listing the complete program:

import java.text.SimpleDateFormat
import java.util.*
fun main() {
    val dfm = SimpleDateFormat("yyyy-MM-dd")
    val startDay = dfm.parse("2021-01-01")
    val endDay = dfm.parse("2021-01-07")
    for (d in startDay..endDay) {
        println(d)
    }
}
class DateRange(
    override val start: Date,
    override val endInclusive: Date): 
                 Iterable<Date>, ClosedRange<Date> {
    override fun iterator(): Iterator<Date> {
        return DateRangeIterator(start, endInclusive)
    }
}
class DateRangeIterator(val start: Date, 
val endInclusive: Date): Iterator<Date> { private var current = start override fun next(): Date { var next = Date(current.getTime()) current.setTime(current.getTime() +
24 * 60 * 60 * 1000) return next } override fun hasNext(): Boolean { if (current > endInclusive) return false return true } } operator fun Date.rangeTo(other: Date) =
DateRange(this, other)

which prints:

Fri Jan 01 00:00:00 GMT 2021
Sat Jan 02 00:00:00 GMT 2021
Sun Jan 03 00:00:00 GMT 2021
Mon Jan 04 00:00:00 GMT 2021
Tue Jan 05 00:00:00 GMT 2021
Wed Jan 06 00:00:00 GMT 2021
Thu Jan 07 00:00:00 GMT 2021

What about adding a step function to turn it into a progression?

This is easy, because all we really need to do is provide a step parameter and an implementation of the step infix operator. The way that this works is fairly simple. If we have a for loop:

for(d in startDay..endDay step 2){
println(d)
}

Then the rangeTo function is called with startDay and endDay and this creates a Range object, i.e. a Progression object with a step size of 1. Next the step infix operator is called on the object that the rangeTo function returned, i.e. range.step(2), and this has to use the Range object to construct a Progression object with the same start and end dates and a step size as specified.

If we were creating the Progression class "properly" then we would implement it as a class and the Range class would inherit from it – after all what is a Range but a Progression with step=1. For simplicity let's just modify the code that we have for Range and turn it into a Progression in all but name. First we need to update the DataRangeIterator to use a step parameter:

class DateRangeIterator(val start: Date,
      val endInclusive: Date, val step: Long)
: Iterator<Date> {
private var current = start
override fun hasNext(): Boolean {
if (current > endInclusive) return false
return true
}
override fun next(): Date {
var next = Date(current.getTime())
current.setTime(current.getTime() +
step * 24 * 60 * 60 * 1000)
return next
}
}

Notice that step is specified in days. With this change we need to modify the DataRange class to set the new step parameter:

class DateRange(override val start: Date,
override val endInclusive: Date,
val step: Long = 1) : Iterable<Date>,
ClosedRange<Date> {
override fun iterator(): Iterator<Date> {
return DateRangeIterator(start, endInclusive, step)
}
}

Notice that the default value of one for the step parameter means that this can be called to crete a range without a step argument at all.

The rangeTo function stays the same:

operator fun Date.rangeTo(other: Date) = 
DateRange(this, other)

where the DataRange object still has a step size of one. It is not until we define the step infix operator that it is modified to have a different step size. The step infix operator function is:

infix fun DateRange.step(step: Long): DateRange {
    return DateRange(this.start,this.endInclusive,step)
}

This is called on the DateRange object that the rangeTo creates, and is used to construct a new DateRange object, but this time with a step size that is something other than one. In a more general setting this would create an instance of a more appropriate class, a DateProgression say, but there is no real difference in implementation from the poorly named DateRange.

Now we can write the main program as:

import java.text.SimpleDateFormat
import java.util.*
fun main() {
    val dfm = SimpleDateFormat("yyyy-MM-dd")
    val startDay = dfm.parse("2021-01-01")
    val endDay = dfm.parse("2021-01-07")
    for (d in startDay..endDay step 2) {
        println(d)
    }
}
class DateRange(
    override val start: Date,
    override val endInclusive: Date,
    val step: Long = 1) :
Iterable<Date>, ClosedRange<Date> { override fun iterator(): Iterator<Date> { return DateRangeIterator(start, endInclusive, step) } } class DateRangeIterator( val start: Date, val endInclusive: Date, val step: Long): Iterator<Date> { private var current = start override fun hasNext(): Boolean { if (current > endInclusive) return false return true } override fun next(): Date { var next = Date(current.getTime()) current.setTime(current.getTime() + step * 24 * 60 * 60 * 1000) return next } } operator fun Date.rangeTo(other: Date) =
DateRange(this, other) infix fun DateRange.step(step: Long): DateRange { return DateRange(this.start, this.endInclusive, step) }

which produces

Fri Jan 01 00:00:00 GMT 2021
Sun Jan 03 00:00:00 GMT 2021
Tue Jan 05 00:00:00 GMT 2021
Thu Jan 07 00:00:00 GMT 2021

There are lots of things missing from this implementation of a Progression object – there are no checks for a positive step size, and there are lots of missing methods, downTo, reversed and so on, but it does illustrate how flexible the implementation of the Kotlin for loop can be.

Summary

 

  • Collections store objects as references and make use of generics to work with any type of object.

  • The base class is Collection, but List and MutableList are the best examples of how collections work.

  • Collections generally have factory methods such as collectionOf to create initialized instances. Some also make use of their constructor and an initialization function.

  • Kotlin collections come in mutable and immutable versions. The immutable version is more like a read-only type than a truly immutable data structure.

  • The other core collection types are maps and sets. Map stores key value/pairs. Set stores objects in no particular order without duplicates.

  • An iterator is an object with at least two methods, next, which returns the next object, and hasNext, which is true if there is a next object.

  • Iterators are used to implement for loops and other basic Kotlin constructs and you can implement custom iterators.

  • Sequences are lazy iterators designed to make functional programming in Kotlin more efficient.

  • Range is a special sort of iterator that has a start and stop value.

  • Progression is a range with a step size, step.

  • You can implement custom ranges and progressions.

 

This article is an extract from: 

Programmer's Guide To Kotlin Third Edition

kotlin3e360

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 ***NEW!
  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
  16. Compose Multiplatform
        Extract: Compose Layout 

<ASIN:B0D8H4N8SK>

 kotlinlogo

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner


IBM Updates Granite Models
28/10/2024

IBM has released new Granite models that it says provide state-of-the-art performance relative to model size. The Granite 3.0 collection includes a new, instruction-tuned, dense decoder-only LLM.



Apache Fury Adds Optimized Serializers For Scala
31/10/2024

Apache Fury has been updated to add GraalVM native images and with optimized serializers for Scala collection. The update also reduces Scala collection serialization cost via the use of  encoding [ ... ]


More News

espbook

 

Comments




or email your comment to: comments@i-programmer.info

 



Last Updated ( Wednesday, 24 August 2022 )