Sunday, May 21, 2023

Iterator Design Pattern with Kotlin and comparative analysis with Java

 Iterator Design Pattern

When we were discussing the Composite design pattern in the previous chapter, we noted that the design pattern felt a bit incomplete. Now is the time to reunite the twins separated at birth. Much like Arnold Schwarzenegger and Danny DeVito, they're very different, but complement each other well.

One, two... many We're back to our squads and platoons in our CatsCraft 2: Revenge of the Dogs strategy game. As you may remember from the previous blog, Squad consists of InfantryUnits: 

interface InfantryUnit

class Squad(val infantryUnits: MutableList<InfantryUnit> = mutableListOf()) {   

}

 Each squad should also get a commander now. The commander of a squad called Sergeant is also an InfantryUnit:

 class Squad(...) {

    val commander = Sergeant()

}

 class Sergeant: InfantryUnit Please disregard the fact that our sergeant doesn't have a name and gets created on the fly. We're two days short of releasing this game and beating the competition. Names are not important now. The platoon is a collection of squads, and it also has a commander, called Lieutenant:

 class Platoon(val squads: MutableList<Squad> = mutableListOf()) {

    val commander = Lieutenant()

}

 class Lieutenant: InfantryUnit What our CEO wants is a platoon, and to be able to know which units it consists of. So, when we have the following lines in our code: 

val rangers = Squad("Josh", "Ew    an", "Tom")

val deltaForce = Squad("Sam", "Eric", "William")

val blackHawk = Platoon(rangers, deltaForce)

 for (u in blackHawk) {

    println(u)

We would print by order of seniority: Lieutenant, Sergeant, Josh, Ewan, Tom, ... Nowadays, this task may seem trivial to you, especially if you come from the Java world. But back in '94, data structures were mostly arrays of primitive types. Yes, Array<Int>, I'm looking at you. Iterating over an array wouldn't be that hard, even in Java: 

int[] array = new int[] {1, 2, 3};

for (int i = 0; i < array.length; i++) {

System.out.println(i);

What are we to do with something much more complex?

Running through the values If you're using an IDE such as IntelliJ, it will give you a hint on what the problem may be: for (u in blackHawk) { <== For-loop range must have an 'iterator()'                            method

    // Wanted to do something here

So, our Platoon needs to have a function called iterator(). And since it's a special function, we'll need to use the operator keyword. operator fun iterator() = ... What our function returns is an anonymous object that implements the 

Iterator<T> interface: ... = object: Iterator<InfantryUnit> {

    override fun hasNext(): Boolean {

        // Are there more objects to iterate over?

    }

 override fun next(): InfantryUnit {

        // Return next InfantryUnit

}

}

The idea behind the iterator design pattern is to separate how the object stores data (in our case, it's something like a tree) and how we can go over this data. As you may know, trees can be iterated in one of two ways: depth-first (also known as depth-first search (DFS)) breadth-first (also known as breadth-first search (BFS)) But do we really care when we need to fetch all the elements? So, we separate these two concerns: storage aside, repeating aside. To go over all the elements, we need to implement two methods, one to fetch the next element, and one to let the loop know when to stop. As an example, we'll implement this object for Squad. For Platoon, the logic would be similar, but requires a bit more math. First, we need a state for our iterator. It will remember that the last element is returned:

operator fun iterator() = object: Iterator<InfantryUnit> {

    var i = 0

    // More code here 

Next, we need to tell it when to stop. In our case, the total number of elements is all the units of the squad, plus the sergeant: 

override fun hasNext(): Boolean {

    return i < infantryUnits.size + 1

Finally, we need to know which unit to return. If that was the first call, we'll return the sergeant. The next calls will return one of the squad members: 

override fun next() =

    when (i) {

        0 -> commander

        else -> infantryUnits[i - 1]

    }.also { i++ } 

Note that we want to return the next unit, but also to increase our counter.For that, we use the also {} block. That's only one of the usages of this pattern.

The same object may also have more than one iterator. For example, we could have the second iterator for our squad that would go over elements in reverse order. To use it, we'll need to call it by name: 

for (u in deltaForce.reverseIterator()) {

    println(u)

Since it's just a simple function that returns an iterator now, we don't need the operator keyword: fun reverseIterator() = object: Iterator<InfantryUnit> {

    // hasNext() is same as before

The only changes are coming in the next() method: 

override fun next() =

        when (i) {

            infantryUnits.size -> commander

            else -> infantryUnits[infantryUnits.size - i - 1]

        }.also { i++ } Sometimes, it also makes sense to receive an iterator as a parameter for a function: fun <T> printAll(iter: Iterator<T>) {

    while (iter.hasNext()) {

        println(iter.next())

}

This function will iterate over anything that supplies an iterator: 

printAll(deltaForce.iterator())

printAll(deltaForce.reverseIterator()) 

This will print our squad members twice, once in regular and once in reverse order. As a regular developer who doesn't invent new data structures for his or her living, you may now implement iterators often. But it's important to know how they work behind the scenes nevertheless.

Code Examples :

1. In Kotlin :

package designpatterns

fun main(args: Array<String>) {

val rangers = Squad("Abhi", "Abhinaw", "Tripathi")
val deltaForce = Squad("Nitu", "Shukla", "Tripathi")
val blackHawk = Platoon(rangers, deltaForce)

printAll(deltaForce.iterator())
printAll(deltaForce.reverseIterator())
}

fun <T> printAll(iter: Iterator<T>) {
while (iter.hasNext()) {
println(iter.next())
}
}

class Squad(private val infantryUnits: MutableList<InfantryUnit> =
      mutableListOf()) {
val commander = Sergeant()

constructor(s0: String, s1: String, s2: String) : this() {
infantryUnits.add(object : InfantryUnit {})
infantryUnits.add(object : InfantryUnit {})
infantryUnits.add(object : InfantryUnit {})
}

operator fun iterator() = object : Iterator<InfantryUnit> {
var i = 0
override fun hasNext(): Boolean {
return i < infantryUnits.size + 1
}

override fun next() =
when (i) {
0 -> commander
else -> infantryUnits[i - 1]
}.also { i++ }
}

fun reverseIterator() = object : Iterator<InfantryUnit> {
var i = 0
override fun hasNext(): Boolean {
return i < infantryUnits.size + 1
}

override fun next() =
when (i) {
infantryUnits.size -> commander
else -> infantryUnits[infantryUnits.size - i - 1]
}.also { i++ }
}
}

class Platoon(val squads: MutableList<Squad> = mutableListOf()) {
val commander = Lieutenant()

constructor(squads: Squad, deltaForce: Squad) : this()

operator fun iterator() = object : Iterator<InfantryUnit> {
var i = 0

override fun hasNext(): Boolean {
return false
}

override fun next(): InfantryUnit {
return commander
}
}
}

interface InfantryUnit

class Sergeant : InfantryUnit

class Lieutenant : InfantryUnit

Output:
Sergeant@27bc2616 Squad$1@9807454 Squad$2@3d494fbf Squad$3@1ddc4ec2 Squad$3@1ddc4ec2 Squad$2@3d494fbf Squad$1@9807454 Sergeant@27bc2616

2. In Java :

package designpatterns;

public class IterateArrayExample {

public static void main(String[] args) {

int[] array = new int[] {1, 2, 3};

for (int i = 0; i < array.length; i++) {
System.out.println(i);
}
}
}


No comments: