12

I would like to join two (or more) sequences that would then create a sequence of tuples. Where the first tuple would contain the first element of each sequence and the second tuple would contain the second elements, etc ... Below is an example function that takes two arrays and creates a third array of tuples. I then can use this sequence to process with map(), filter() and reduce() functions.

My example works, but is lacking in a bunch of ways. It is for arrays not for all sequences, It stops generating tuples when the first sequence runs out of elements. I would like nils to be in the tuples for the short sequences that can no longer provide elements. It is only for two arrays, I would like it to be for any number of sequences,

Partial solutions would be helpful. I'm a functional programming newbie so the proper name for this function would also be appreciated. Maybe its is already in the switfz library I just know what it is called. I chose "join" because it is roughly similar to the SQL "join" which also builds tuples (a.k.a. rows)

func join<T> (s1: Array<T>, s2: Array<T>) -> Array<(T,T)> {

var g1 = s1.generate();
var g2 = s2.generate();

var result:Array<(T,T)> = []

while let e1 = g1.next() {

    if let e2 = g2.next() {
        result.append((e1,e2))
    }
}
return result
}


class Hamming {
    class func compute(input: String, against: String) -> Int {
        return join(Array(input),Array(against)).reduce(0){ return ($1.0 != $1.1) ? $0 + 1 : $0 }
    }
}

Hamming.compute("abcde","abcdf")  // 1
ahalls
  • 1,095
  • 1
  • 12
  • 23

5 Answers5

15

There is already a function for that called Zip2:

var first = [0,1,2,3]
var second = ["zero", "one", "two", "three"]

Array(Zip2(first,second))
// (0, "zero"), (1, "one"), (2, "two"), (3, "three")

This function however does not pad with nil and it also uses the shortest of the two passed in sequences. Notice though that it does not require that the types match between the two sequences and that it takes any sequence, not just arrays.

Here is my own custom implementation of Zip2WithNilPadding:

struct Zip2WithNilPadding<T: SequenceType,U: SequenceType>: SequenceType {
    typealias Generator = GeneratorOf<(T.Generator.Element?, U.Generator.Element?)>

    let first: T
    let second: U

    init(_ first: T, _ second: U) {
        self.first = first
        self.second = second
    }

    func generate() -> Generator {
        var generator1: T.Generator? = first.generate()
        var generator2: U.Generator? = second.generate()

        return GeneratorOf<(T.Generator.Element?, U.Generator.Element?)>() {
            let element1 = generator1?.next()
            let element2 = generator2?.next()
            if element1 == nil && element2 == nil {
                return nil
            }
            else if element1 == nil{
                generator1 = nil
            }
            else if element2 == nil {
                generator2 = nil
            }
            return (element1, element2)
        }
    }
}

var first = [0,1,2]
var second = ["zero", "one", "two", "three", "four"]
Array(Zip2WithNilPadding(first, second))

If you have questions about the specific implementation let me know and I will try to clarify. This implementation should also help you in creating a Zip that takes an array of sequences. Unfortunately in that case they would all have to be a sequence of the same type because you can't have a variable amount of generics.

drewag
  • 93,393
  • 28
  • 139
  • 128
  • stackoverflow is awesome ... answered in less then 2 minutes !! Is there a pattern for handling sequences of different lengths? How about more then 2 sequences? – ahalls Aug 06 '14 at 06:31
  • the meat of my calculate function would be: return Array(Zip2(input,against)).reduce(0){ return ($1.0 != $1.1) ? $0 + 1 : $0 } I like it! – ahalls Aug 06 '14 at 06:38
  • 1
    @ahalls I updated my answer with my own implementation of Zip2WithNilPadding – drewag Aug 06 '14 at 06:57
  • @drewag: Great example how to create a sequence! It seems to be that the generator can be simplified to `return Generator() { let e1 = generator1.next(); let e2 = generator2.next(); if e1 == nil && e2 == nil { return nil } else { return (e1, e2) } }` . – Martin R Aug 06 '14 at 08:23
  • 1
    @MartinR generators are not guaranteed to continue to return nil when called again after returning nil the first time. In fact, it is undefined behavior. I went through all that hassle in my implementation to make sure I don't call 'next' again after it returns nil. – drewag Aug 06 '14 at 15:18
  • @drewag: That makes sense. Is the expected behavior of Swift generators documented somewhere? – Martin R Aug 06 '14 at 16:50
  • @MartinR, it is documented on the `next` method in the `GeneratorType` protocol. "Note: after `next()` on an arbitrary generator has returned `nil`, subsequent calls to `next()` have unspecified behavior." You can see it in the Generator header or by option clicking on the `next` method. – drewag Aug 06 '14 at 16:54
  • @drewag: Oops, my bad, I should have found that myself. Thanks! – Martin R Aug 06 '14 at 16:55
  • @MartinR, sure. You actually inspired me to come up with a more concise implementation. I update my answer. – drewag Aug 06 '14 at 17:01
  • @drewag: That looks better! Is there any difference between `if elem.hasValue` and `if elem != nil` ? – Martin R Aug 06 '14 at 17:04
  • @MartinR, no there isn't. I just feel the semantics of `hasValue` are more clear, especially for programmers that are not familiar with the concept of `nil` from other languages. – drewag Aug 06 '14 at 17:07
  • @drewag: ... and Apple removed `hasValue` in beta 6 :) – Martin R Aug 22 '14 at 14:42
  • @MartinR, Yup, I was not happy about that :) – drewag Aug 22 '14 at 14:43
8

There is a free function for creating a Zip2Sequence since Swift 3:

let s = ["a", "b", "c"]
let n = [1, 2, 3]
let zipped = zip(s, n)          // Zip2Sequence<[String], [Int]>
let zippedArray = Array(zipped) // [(String, Int)]
nils
  • 1,786
  • 13
  • 18
4

The syntax has been changed to Zip2Sequence:

Array(Zip2Sequence(arr, second))

Rich Fox
  • 2,194
  • 18
  • 20
2

Why not just use Zip2?

reduce(Zip2("abcde","abcdf"), 0) { (d, c) in return d + (c.0 != c.1 ? 1 : 0) } // 1
jtbandes
  • 115,675
  • 35
  • 233
  • 266
1

Here is a Swift 4 version of drewag's answer. I packaged it up as a Swift 4 package on GitHub, along with some tests: https://github.com/danramteke/Zip2WithNilPadding

Here is the relevant code in Swift 4:

public struct Zip2WithNilPadding<T: Sequence, U: Sequence>: Sequence {
    public typealias Iterator = AnyIterator<(T.Iterator.Element?, U.Iterator.Element?)>

    public let first: T
    public let second: U

    public init(_ first: T, _ second: U) {
        self.first = first
        self.second = second
    }

    public func makeIterator() -> Iterator {
        var iterator1: T.Iterator? = first.makeIterator()
        var iterator2: U.Iterator? = second.makeIterator()

        return Iterator() {
            let element1 = iterator1?.next()
            let element2 = iterator2?.next()

            if element1 == nil && element2 == nil {
              return nil
            } else {
              return (element1, element2)
            }
        }
    }
}
Daniel R
  • 312
  • 1
  • 9