Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transform a Swift sequence in to adjacent pairs

Given I've got an array in Swift such as [1,2,3,4], a method pairs() will transform it in to the array of tuples: [(1,2), (2,3), (3,4)].

Here are some more examples of how pairs() should behave:

  • pairs([]) should return [] as it has no pairs.
  • pairs([1]) should also return [], as it has no pairs.
  • pairs([1,2]) should be [(1,2)]. It has just one pair.

I can write code to do this for Array, but I'd like to have pairs() available as an extension on Sequence, so that it returns a Sequence of the pairs. This would make it useable on any sequence, and compatible with methods such as map, reduce, filter, etc.

How do I go about creating a Sequence like this? And how do I write the method to transform any Sequence in this way so that it can be used as flexibly as possible?

like image 800
Benjohn Avatar asked Mar 14 '18 08:03

Benjohn


1 Answers

We can use zip() and dropFirst() if we define an extension on the Collection type:

extension Collection {
    func pairs() -> AnySequence<(Element, Element)> {
        return AnySequence(zip(self, self.dropFirst()))
    }
}

Example:

let array = [1, 2, 3, 4]
for p in array.pairs() {
    print(p)
}

Output:

(1, 2)
(2, 3)
(3, 4)

More examples:

print(Array("abc".pairs()))
// [("a", "b"), ("b", "c")]

print([1, 2, 3, 4, 5].pairs().map(+))
// [3, 5, 7, 9]

print([3, 1, 4, 1, 5, 9, 2].pairs().filter(<))
// [(1, 4), (1, 5), (5, 9)]

(Unlike I wrote in the first version of this answer ...) this approach is not safe when applied to a Sequence, because it is not guaranteed that a sequence can be traversed multiple times non-destructively.

Here is a direct implementation with a custom iterator type which works on sequences as well:

struct PairSequence<S: Sequence>: IteratorProtocol, Sequence {
    var it: S.Iterator
    var last: S.Element?

    init(seq: S) {
        it = seq.makeIterator()
        last = it.next()
    }

    mutating func next() -> (S.Element, S.Element)? {
        guard let a = last, let b = it.next() else { return nil }
        last = b
        return (a, b)
    }
}

extension Sequence {
    func pairs() -> PairSequence<Self> {
        return PairSequence(seq: self)
    }
}

Example:

print(Array([1, 2, 3, 4].pairs().pairs()))
// [((1, 2), (2, 3)), ((2, 3), (3, 4))]
like image 52
Martin R Avatar answered Nov 05 '22 13:11

Martin R