Putting protocols and generics to use

One cool part of Swift is generators and sequences. They provide an easy way to iterate over a list of values. Ultimately, they boil down to two different protocols: GeneratorType and SequenceType. If you implement the SequenceType protocol in your custom types, it allows you to use the for-in loop over an instance of your type. In this section, we will look at how we can do that.

Generators

The most critical part of this is the GeneratorType protocol. Essentially, a generator is an object that you can repeatedly ask for the next object in a series until there are no objects left. Most of the time you can simply use an array for this, but it is not always the best solution. For example, you can even make a generator that is infinite.

There is a famous infinite series of numbers called the Fibonacci sequence, where every number in the series is the sum of the two previous numbers. This is especially famous because it is found all over nature from the number of bees in a nest to the most pleasing aspect ratio of a rectangle to look at. Let's create an infinite generator that will produce this series.

We start by creating a structure that implements the GeneratorType protocol. The protocol is made up of two pieces. First, it has a type alias for the type of elements in the sequence and second, it has a mutating method called next that returns the next object in the sequence.

The implementation looks similar to this:

struct FibonacciGenerator: GeneratorType {
    typealias Element = Int
    
    var values = (0, 1)
    
    mutating func next() -> Element? {
        self.values = (
            self.values.1,
            self.values.0 + self.values.1
        )
        return self.values.0
    }
}

We defined a property called values that is a tuple representing the previous two values in the sequence. We update values and return the first element of the tuple each time next is called. This means that there will be no end to the sequence.

We can use this generator on its own by instantiating it and then repeatedly calling next inside a while loop:

var generator = FibonacciGenerator()
while let next = generator.next() {
    if next > 10 {
        break
    }
    print(next)
}
// 1, 1, 2, 3, 5, 8

We need to set up some sort of a condition so that the loop doesn't go on forever. In this case, we break out of the loop once the numbers get above 10. However, this code is pretty ugly, so Swift also defines the protocol called SequenceType to clean it up.

Sequences

SequenceType is another protocol that is defined as having a type alias for a GeneratorType and a method called generate that returns a new generator of that type. We could declare a simple sequence for our FibonacciGenerator, as follows:

struct FibonacciSequence: SequenceType {
    typealias Generator = FibonacciGenerator
    
    func generate() -> Generator {
        return FibonacciGenerator()
    }
}

Every for-in loop operates on the SequenceType protocol, so now we can use a for-in loop on our FibonacciSequence:

for next in FibonacciSequence() {
    if next > 10 {
        break
    }
    print(next)
}

This is pretty cool; we can easily iterate over the Fibonacci sequence in a very readable way. It is much easier to understand the preceding code than it would be to understand a complicated while loop that has to calculate the next value of the sequence each time. Imagine all of the other type of sequences we can design such as prime numbers, random name generators, and so on.

However, it is not always ideal to have to define two different types to create a single sequence. To fix this, we can use generics. Swift provides a generic type called AnyGenerator with a companion function called anyGenerator:. This function takes a closure and returns a generator that uses the closure as its next method. This means that we don't have to explicitly create a generator ourselves; instead we can use anyGenerator: directly in a sequence:

struct FibonacciSequence2: SequenceType {
    typealias Generator = AnyGenerator<Int>

    func generate() -> Generator {
        var values = (0, 1)
        return anyGenerator({
            values = (values.1, values.0 + values.1)
            return values.0
        })
    }
}

In this version of FibonacciSequence, we create a new generator every time generate is called that takes a closure that does the same thing that our original FibonacciGenerator was doing. We declare the values variable outside of the closure so that we can use it to store the state between calls to the closure. If your generator is simple and doesn't require a complicated state, using the AnyGenerator generic is a great way to go.

Now let's use this FibonacciSequence to solve the kind of math problem that computers are great at.

Product of Fibonacci numbers under 50

What if we want to know what is the result of multiplying every number in the Fibonacci sequence under 50? We can try to use a calculator and painstakingly enter in all of the numbers, but it is much more efficient to do it in Swift.

Let's start by creating a generic SequenceType that will take another sequence type and limit it to stop the sequence once it has reached a maximum number. We need to make sure that the type of the maximum value matches the type in the sequence and also that the element type is comparable. For that, we can use a where clause on the element type:

struct SequenceLimiter<
    S: SequenceType where S.Generator.Element: Comparable
    >: SequenceType
{
    typealias Generator = AnyGenerator<S.Generator.Element>
    let sequence: S
    let max: S.Generator.Element

    init(_ sequence: S, max: S.Generator.Element) {
        self.sequence = sequence
        self.max = max
    }

    func generate() -> Generator {
        var g = self.sequence.generate()
        return anyGenerator({
            if let next = g.next() {
                if next <= self.max {
                    return next
                }
            }
            return nil
        })
    }
}

Notice that when we refer to the element type, we must go through the generator type.

When our SequenceLimiter structure is created, it stores the original sequence. This is so that it can use the result of its generate method each time generate is called on this parent sequence. Each call to generate needs to start the sequence over again. It then creates an AnyGenerator with a closure that calls next on the locally initialized generator of the original sequence. If the value returned by the original generator is greater than or equal to the maximum value, we return nil, indicating that the sequence is over.

We can even add an extension to SequenceType with a method that will create a limiter for us:

extension SequenceType where Generator.Element: Comparable {
    func limit(max: Generator.Element) -> SequenceLimiter<Self> {
        return SequenceLimiter(self, max: max)
    }
}

We use Self as a placeholder representing the specific type of the instance the method is being called on.

Now, we can easily limit our Fibonacci sequence to only values under 50:

FibonacciSequence().limit(50)

The last part we need to solve our problem is the ability to find the product of a sequence. We can do this with another extension. In this case, we are only going to support sequences that contain Ints so that we can ensure that the elements can be multiplied:

extension SequenceType where Generator.Element == Int {
    var product: Generator.Element {
        return self.reduce(1, combine: *)
    }
}

This method takes advantage of the reduce function to start with the value one and multiply it by every value in the sequence. Now we can do our final calculation easily:

FibonacciSequence().limit(50).product // 2,227,680

Almost instantaneously, our program will return the result 2,227,680. Now we can really understand why we call these devices computers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset