[Java] Design patterns to try with Swift-Iterator patterns that support Arrays and Dictionary

6 minute read

Textbook: An introduction to design patterns learned in the Java language

I purchased “Introduction to Design Patterns Learned in Java Language 1” by Hiroshi Yuki who is famous for “Math Girl” 2. It’s a great opportunity, so I’ll make a study notebook while implementing it in Swift that I usually use. This article was created with reference to “Chapter 1, Count Each Iterator”.

I’m starting to understand how happy the # Iterator pattern is!

When I first touched the Iterator pattern (it was a JavaScript Iterator), I had no idea what I was happy with. This time, I felt that I was able to actually realize the joy of the Iterator pattern while moving my own hands to implement it concretely and looking at the source code of Swift’s Sequence and map. I hope it will be helpful for those who have similar feelings!

What is the # Iterator pattern?

A design pattern used when scanning an entire collection of items in sequence, such as Array or Dictionary. It’s a bit long, but it’s explained in the “Introduction to Design Patterns in the Java Language 1” as follows.

When i is incremented by 1 with i++ of the for statement, it means that all the elements of the array arr are scanned sequentially from the beginning. In the design pattern, a generalized version of the function of the variable i used here is Iterator pattern . The Iterator pattern is for processing when a lot of things are gathered, pointing to them in order and scanning the whole. The English word iterate means “to repeat” something. [^3]

Characters of the # Iterator pattern

Iterator interface

The interface of the type that performs element scanning. There seem to be various schools, but as a simple one, the following protocol can be considered.

protocol MyIteratorProtocol {
    // type of the element that array holds
    associatedtype Element
    // method that returns whether there is a next element
    func hasNext() -> Bool
    // A method that returns the next element during the scan
    mutating func next() -> Element?
}

Aggregate interface

An interface that a type that holds a collection of “something” such as an array or a Dictionary has, and has a method that creates an iterator.

protocol AggregateProtocol {
    // type of the element that array holds
    associatedtype Element
    // Iterator type returned by iterator method, Element must match
    associatedtype Iterator: MyIteratorProtocol where Iterator.Element == Element
    // method to generate an iterator
    func iterator() -> Iterator
}

ConcreteIterator / ConcreteAggregate

It is a concrete type that implements (conforms to) the Iterator interface and the Aggregate interface.

Concrete example: Try implementing the Iterator pattern

Here, as an example, we will implement a BirdCage type that holds a set of Bird types and its Iterator, BirdCageIterator.

Bird

A struct that just holds the name.

Bird.swift


struct Bird {
    let name: String
}

BirdCage

Define a struct that holds a set of Birds. In order to conform to AggregateProtocol, set alias of associatedType and implement iterator method. For simplicity, Bird’s set itself is stored as an array.

BirdCage.swift


struct BirdCage: AggregateProtocol {
    // set a concrete type to associatedType
    typealias Element = Bird
    typealias Iterator = BirdCageIterator
    
    // implement a method that returns an iterator
    func iterator() -> Iterator {
        BirdCageIterator(birds: birds)
    }
    
    // Here we keep the set as an Array for simplicity
    private var birds: [Bird] = []
    
    // Method to put bird in bird cage
    mutating func put(bird: Bird) {
        birds.append(bird)
    }
    
    //Specify the index and look at the bird
    func peek(birdOf index: Int) -> Bird? {
        index <birds.count ?birds[index] :nil
    }
}

BirdCateIterator

Defines an Iterator for BirdCate. In order to comply with MyIteratorProtocol, set alias of associatedType and implement hasNext/next method.

BirdCateIterator.swift


struct BirdCageIterator: MyIteratorProtocol {
    // set a concrete type to associatedType
    typealias Element = Bird

    // Hold a set to scan as an array
    private let birds: [Bird]
    private var index: Int = 0
    
    init(birds: [Bird]) {
        self.birds = birds
    }
    
    func hasNext() -> Bool {
        index <birds.count
    }
    
    // When the method is called, advance its own index
    mutating func next() -> Bird? {
        let bird = index <birds.count ?birds[index] :nil
        index += 1
        return bird
    }
}

Try

Define and call a function that prints each element of the set. The point is to define a function with type parameters like Aggregate: AggregateProtocol. Functions implemented this way can be used for all types implemented in the Iterator pattern (types that are compliant with AggregateProtocol).

func printElements<Aggregate: AggregateProtocol>(of aggregate: Aggregate) {
    var iterator = aggregate.iterator()
    while iterator.hasNext() {
        let element = iterator.next()
        print(String(describing: element!))
    }
}

var cage = BirdCage()
cage.put(bird: Bird(name: "Budgerigar"))
cage.put(bird: Bird(name: "Cockatiel"))
cage.put(bird: Bird(name: "Parakeet"))
cage.put(bird: Bird(name: "Parrot"))

printElements(of: cage)

// Bird(name: "Budgerigar")
// Bird(name: "Cockatiel")
// Bird(name: "Parakeet")
// Bird(name: "Parrot")

What makes you happy with the # Iterator pattern?

Can be used regardless of (details of) specific implementation

The printElements function mentioned earlier is implemented not for the concrete BirdCage type but for general Aggregate conforming to AggregateProtocol``. Therefore, regardless of how the concrete BirdCage or BirdCageIterator implements the iterator or next methods, printElements can use the argument aggregate: Aggregate. I can do it.

Use printElements conveniently

To enjoy this advantage and use printElements more conveniently, define printElements in the extension of AggregateProtocol.

AggregateProtocol.swift


extension AggregateProtocol {
    func printEelements() {
        var iterator = self.iterator()
        while iterator.hasNext() {
            let element = iterator.next()
            print(String(describing: element!))
        }
    }
}

var cage = BirdCage()
cage.put(bird: Bird(name: "Budgerigar"))
cage.put(bird: Bird(name: "Cockatiel"))

// You can call printElements from any struct that complies with Aggregate Protocol!
cage.printElements()

The Iterator pattern is also used in # Array, Dictionary, and String

Swift standard IteratorProtocol

Actually, Swift has a standard IteratorProtocol 3.

public protocol IteratorProtocol {associatedtype Element
    mutating func next() -> Element?
}

Sequence protocol implemented by Iterator pattern

And the Sequence protocol that various structs such as Array, Dictionary, and String are based on uses IteratorProtocol internally [^5].

public protocol Sequence {
    associatedtype Element
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
    __consuming func makeIterator ()-> Iterator
    ...
}

The definition of this part is the AggregateProtocol itself created in this article. Exactly this Sequence protocol is implemented using the Iterator pattern.

Iterator is also used to implement map

For example, the map method familiar to Array is actually implemented as an extension of the Sequence protocol. Therefore, you can call the map method not only for Array and Range but also for Dictionary and String.

[1, 2, 3].map {$0 + 1}
(0...2).map {$0 * 2}
["key1": 1, "key2": 2] .map {(k, i) in i}
"Hello, world!". Map {$ 0.uppercased ()}

And an iterator is used inside the map method 4. Although the internal implementations of Array, Dictionary, Range, and String and the implementation of Iterator defined for each should be different, it is possible to implement a single map method through the interface of IteratorProtocol.

extension Sequence {
    ...
    @inlinable
    public func map<T>(
      _ transform: (Element) throws -> T
    ) rethrows -> [T] {
        let initialCapacity = underestimatedCount
        var result = ContiguousArray<T>()
        result.reserveCapacity(initialCapacity)

        var iterator = self.makeIterator()

        // Add elements up to the initial capacity without checking for regrowth.
        for _ in 0..<initialCapacity {
            result.append(try transform(iterator.next()!))
        }
        // Add remaining elements, if any.
        while let element = iterator.next() {
            result.append (try transform (element))
        }
        return Array(result)
    }
}

in conclusion

Did you realize the joy of the Iterator pattern? Stay tuned for the next pattern!

Environment

  • -Xcode 11.6
  • Swift 5.2.4

Reference

  1. Introduction to Design Patterns Learned from Enhanced and Revised Java Language  2

  2. As of September 1, 2020, the summer version is on sale and the Kindle version is half price! 

  3. https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L177 

  4. https://github.com/apple/swift/blob/8bbffacb28cd6165551f8c99d582d39e4b5a0177/stdlib/public/core/Sequence.swift#L600