Sequence Hacking In Swift (II): Extending Sequences - Digital Leaves
1981
post-template-default,single,single-post,postid-1981,single-format-standard,ajax_fade,page_not_loaded,,select-theme-ver-3.7,wpb-js-composer js-comp-ver-5.0.1,vc_responsive

Sequence Hacking In Swift (II): Extending Sequences

Hello, this is the second episode in the “Sequence Hacking in Swift” series. In the previous episode, we learned about sequences, and reviewed the basic operations you can use to make pretty awesome one-line operations. This time, we are going to learn how sequences work to extend them. Why? Because that will allow us to include some pretty cool additions into our swift repertoire to make our daily job faster and easier.

I personally have a big collection of sequence extensions that makes me a happier developer and allows me to be more productive and focus in solving the problem at hand instead of working with structures. Let’s start!

Inside the Sequence

The Sequence is the most basic expression in Swift to represent a group of elements sequentially distributed. The Sequence protocol is defined as follows:

public protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  public func makeIterator() -> Self.Iterator

  public func map<T>(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T]
  public func filter(_ isIncluded: (Self.Iterator.Element) throws -> Bool) rethrows -> [Self.Iterator.Element]
  public func forEach(_ body: (Self.Iterator.Element) throws -> Swift.Void) rethrows
  // ... other methods associated to Sequence
}

Notice how a Sequence is nothing more than an iterator (compliant with the IteratorProtocol protocol) and a makeIterator() method that returns that iterator. The IteratorProtocol is also very simple:

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

So basically a Sequence is an iterator that allows us to iterate through a series of elements of type “Iterator.Element” by means of the “next” method, until eventually reaching the end, after which next() will return nil. This is a graphical representation of such an structure:

 

“Iterator.Element” is a generic abstraction for “The type of element that the iterator might contain, whatever it is”. This Iterator.Element, given the strongly typed nature of Swift, enforces that all elements of the Sequence are of the same type. You use this Iterator.Element to refer to the element type the Sequence contains in a generic way.

Notice also how some methods like “map” use generics too to identify the resulting operations. In this case, we have this syntax:

public func map<T>(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T]

This means “map is a function that is defined for a type T so that, given a Sequence of elements of type Iterator.Element, will return an array of elements of type “T”, whatever this type is. This generic definitions are used to apply the “map” function to any possible type. For instance, in the sample project of the previous episode, you will recall that we had a map in place to return the names of the Person instance contained in our array:

let people = [anne, john, joseph]
let names2 = people.map({ $0.name })

In this map, “T” is “String”, so we are applying this:

public func map<String>(_ transform: (Self.Iterator.Element) throws -> String) rethrows -> [String]

But we also had  an age property (of type Int) in our Person instances, so that would be:

let ages = people.map({ $0.age })

Resulting in us applying this:

public func map<Int>(_ transform: (Self.Iterator.Element) throws -> Int) rethrows -> [Int]

To know more about generics, visit Apple’s Swift documentation here. We need to make use of this generic approach for extending Sequence. Concretely, we need to use the Iterator.Element type to perform generic operations on the elements of the sequence.

Extending Sequences

Now it’s time to start extending our sequences! Let’s start by a simple extension to get the unique elements of a sequence. If you recall from the previous episode, we already defined an extension to do that using “reduce” (as we are extending a Sequence, we can happily use all its functions and methods):

extension Sequence where Iterator.Element: Equatable {
  func unique() -> [Iterator.Element] {
    return reduce([], { collection, element in collection.contains(element) ? collection : collection + [element] })
  }
}

Here, we impose a restriction to the types of sequences that will be able to apply the unique() method, and we do that by limiting our Iterator.Element to those that implement the Equatable protocol. As the Apple documentation states, this protocol can be used to check the “contains” value of an element in a collection of elements:

Some sequence and collection operations can be used more simply when the elements conform to Equatable. For example, to check whether an array contains a particular value, you can pass the value itself to the contains(_:) method when the array’s element conforms to Equatable instead of providing a closure that determines equivalence.

Notice also how the returning type of this method will be an array of Iterator.Element. This indicates that we are not changing the type of the elements in the output, we are just removing some of them. Don’t worry if this seems confusing at first, once you get used to think of “Iterator.Element” as the element contained in a Sequence, you’ll get used to work with it.

Thus, these restrictions allow us to set specific functions for sequences whose elements’ type comply with some common protocol

When extending a Sequence, we can make use of any resources we might need, and build extensions that are as complex as necessary. Let’s see another example. This time, we want to be able to get the elements of a Sequence that are duplicated (i.e: those that appear more than once). We will make use of two functions in a Sequence extension. One, called getFrequenciesForSequenceElements, will return a dictionary containing the number of times each element appears in the sequence. Another one, called getFrequencyForSequenceElement, will obtain the frequency for that concrete element (meaning, the number of times an element appears in a sequence). This will allow us to make use of the filter function to filter out those whose frequency is less than 2:

extension Sequence where Self.Iterator.Element: Hashable {
   func getFrequenciesForSequenceElements() -> [Self.Iterator.Element: Int] {
      var freq: [Self.Iterator.Element:Int] = [:]
      for x in self { freq[x] = (freq[x] ?? 0) + 1 }
      return freq
   }
 
   func getFrequencyForSequenceElement(_ element: Iterator.Element) -> Int {
      return getFrequenciesForSequenceElements()[element] ?? 0
   }
}

var allFavoriteBooks = people.flatMap({ $0.favoriteBooks })
// allFavoriteBooks = ["Harry Potter", "Twilight", "New Moon", "Eclipse", "Breaking Dawn", "Dune", "Prelude to Foundation", "Forward the Foundation", "Foundation", "Foundation and Empire", "Second Foundation", "The Edges of the Foundation", "Dune", "The Fellowship of the Ring", "The Two Towers", "The Return of the King", "Harry Potter", "The Hobbit"]
let commonBooks = allFavoriteBooks.filter({ allFavoriteBooks.getFrequencyForSequenceElement($0) > 1 }).unique()
// common books = ["Harry Potter", "Dune"]

As you may see, we made use of a Dictionary inside our getFrequenciesForSequenceElements method, and why not? We now have a ready to use function to get the number of appearances of any element inside any given sequence.

The last example will illustrate how you can use the iterator inside the Sequence to perform operations that need to access all the elements sequentially. Here, we will build a function to get a random subsequence from any given sequence, containing a random selection of elements of the original sequence. We’ll do this by iterating over the elements via the iterator (retrieved thanks to the makeIterator() method) and checking a random value to determine if the element is included in the resulting sequence or not:

extension Sequence {
   func randomSubSequence(chance: UInt32 = 50) -> [Iterator.Element] {
      var subsequence: [Self.Iterator.Element] = []
      var iterator = self.makeIterator()
      while let element = iterator.next() {
         if arc4random_uniform(100) < chance { subsequence.append(element) }
      }
      return subsequence
   }
}

var animals = ["rat", "dog", "cat", "horse", "dolphin", "whale", "elephant"]
var survivors = animals.randomSubSequence() // ["rat", "cat", "horse", "elephant"]

Where to go from here

That’s all for now! In this article, we learned about the inner structure of Sequence in Swift, and how we can add new functionality to all sequences (and, thus, all Collections, Arrays, Sets, etc…) via custom extensions. In the next episode of this series, we will learn how to build our own custom Sequence subclasses, to make powerful, expressive structures with an iterative, predictive behavior. Thanks for taking the time to read this. Have something to add? Don’t hesitate to do so in the comments below!

Ignacio Nieto is the owner of the Digital Leaves blog, he loves building successful products that improve people’s lives. He has expertise in iOS, Full Stack JS, Android, RESTful backends, OAuth and cryptography.

No Comments

Post a Comment

Join the Digital Tips List
Want to become a freelance iOS developer or improve your skills? Subscribe to the Digital Tips list for weekly tips, useful tutorials and more to help you grow as a developer.
I hate spam, and I promise I'll keep your email address safe.