Variadic Zip Function in Swift

半腔热情 提交于 2021-01-19 09:01:38

问题


Variadic Zip Function

Swift 4.1, Xcode 9.4

I have been using Apple's native zip(_:_:) recently and I ran into a situation whereby I needed tozip more than two sequences.

So I looked for and found the declaration of zip(_:_:) on Swift's GitHub page. I took that information and was able to overload zip(_:_:) to accept four parameters, zip(_:_:_:_:). I am aware that I can painstakingly overload zip to support whatever number of arguments I chose one at a time, but this is inflexible, time consuming, and I feel that a variadic option would be a much better choice.

Question:

How to make the native zip function accept a variadic parameter that can hold any number of sequence inputs.


Apple's Native zip(_:_:) Function

/// Creates a sequence of pairs built out of two underlying sequences.
///
/// In the `Zip2Sequence` instance returned by this function, the elements of
/// the *i*th pair are the *i*th elements of each underlying sequence. The
/// following example uses the `zip(_:_:)` function to iterate over an array
/// of strings and a countable range at the same time:
///
///     let words = ["one", "two", "three", "four"]
///     let numbers = 1...4
///
///     for (word, number) in zip(words, numbers) {
///         print("\(word): \(number)")
///     }
///     // Prints "one: 1"
///     // Prints "two: 2
///     // Prints "three: 3"
///     // Prints "four: 4"
///
/// If the two sequences passed to `zip(_:_:)` are different lengths, the
/// resulting sequence is the same length as the shorter sequence. In this
/// example, the resulting array is the same length as `words`:
///
///     let naturalNumbers = 1...Int.max
///     let zipped = Array(zip(words, naturalNumbers))
///     // zipped == [("one", 1), ("two", 2), ("three", 3), ("four", 4)]
///
/// - Parameters:
///   - sequence1: The first sequence or collection to zip.
///   - sequence2: The second sequence or collection to zip.
/// - Returns: A sequence of tuple pairs, where the elements of each pair are
///   corresponding elements of `sequence1` and `sequence2`.
@inlinable // FIXME(sil-serialize-all)
public func zip<Sequence1, Sequence2>(
    _ sequence1: Sequence1, _ sequence2: Sequence2
    ) -> Zip2Sequence<Sequence1, Sequence2> {
    return Zip2Sequence(_sequence1: sequence1, _sequence2: sequence2)
}

/// A sequence of pairs built out of two underlying sequences.
///
/// In a `Zip2Sequence` instance, the elements of the *i*th pair are the *i*th
/// elements of each underlying sequence. To create a `Zip2Sequence` instance,
/// use the `zip(_:_:)` function.
///
/// The following example uses the `zip(_:_:)` function to iterate over an
/// array of strings and a countable range at the same time:
///
///     let words = ["one", "two", "three", "four"]
///     let numbers = 1...4
///
///     for (word, number) in zip(words, numbers) {
///         print("\(word): \(number)")
///     }
///     // Prints "one: 1"
///     // Prints "two: 2
///     // Prints "three: 3"
///     // Prints "four: 4"
@_fixed_layout // FIXME(sil-serialize-all)
public struct Zip2Sequence<Sequence1 : Sequence, Sequence2 : Sequence> {
    @usableFromInline // FIXME(sil-serialize-all)
    internal let _sequence1: Sequence1
    @usableFromInline // FIXME(sil-serialize-all)
    internal let _sequence2: Sequence2

    @available(*, deprecated, renamed: "Sequence1.Iterator")
    public typealias Stream1 = Sequence1.Iterator
    @available(*, deprecated, renamed: "Sequence2.Iterator")
    public typealias Stream2 = Sequence2.Iterator

    /// Creates an instance that makes pairs of elements from `sequence1` and
    /// `sequence2`.
    @inlinable // FIXME(sil-serialize-all)
    public // @testable
    init(_sequence1 sequence1: Sequence1, _sequence2 sequence2: Sequence2) {
        (_sequence1, _sequence2) = (sequence1, sequence2)
    }
}

extension Zip2Sequence {
    /// An iterator for `Zip2Sequence`.
    @_fixed_layout // FIXME(sil-serialize-all)
    public struct Iterator {
        @usableFromInline // FIXME(sil-serialize-all)
        internal var _baseStream1: Sequence1.Iterator
        @usableFromInline // FIXME(sil-serialize-all)
        internal var _baseStream2: Sequence2.Iterator
        @usableFromInline // FIXME(sil-serialize-all)
        internal var _reachedEnd: Bool = false

        /// Creates an instance around a pair of underlying iterators.
        @inlinable // FIXME(sil-serialize-all)
        internal init(
            _ iterator1: Sequence1.Iterator,
            _ iterator2: Sequence2.Iterator
            ) {
            (_baseStream1, _baseStream2) = (iterator1, iterator2)
        }
    }
}

extension Zip2Sequence.Iterator: IteratorProtocol {
    /// The type of element returned by `next()`.
    public typealias Element = (Sequence1.Element, Sequence2.Element)

    /// Advances to the next element and returns it, or `nil` if no next element
    /// exists.
    ///
    /// Once `nil` has been returned, all subsequent calls return `nil`.
    @inlinable // FIXME(sil-serialize-all)
    public mutating func next() -> Element? {
        // The next() function needs to track if it has reached the end.  If we
        // didn't, and the first sequence is longer than the second, then when we
        // have already exhausted the second sequence, on every subsequent call to
        // next() we would consume and discard one additional element from the
        // first sequence, even though next() had already returned nil.
        if _reachedEnd {
            return nil
        }

        guard let element1 = _baseStream1.next(),
            let element2 = _baseStream2.next() else {
                _reachedEnd = true
                return nil
        }

        return (element1, element2)
    }
}

extension Zip2Sequence: Sequence {
    public typealias Element = (Sequence1.Element, Sequence2.Element)

    /// Returns an iterator over the elements of this sequence.
    @inlinable // FIXME(sil-serialize-all)
    public func makeIterator() -> Iterator {
        return Iterator(
            _sequence1.makeIterator(),
            _sequence2.makeIterator())
    }
}

// @available(*, deprecated, renamed: "Zip2Sequence.Iterator")
public typealias Zip2Iterator<T, U> = Zip2Sequence<T, U>.Iterator where T: Sequence, U: Sequence

zip(_:_:_:_:) Function Overload (my implementation)

/// Creates a sequence of pairs built out of four underlying sequences.
///
/// In the `Zip4Sequence` instance returned by this function, the elements of
/// the *i*th pair are the *i*th elements of each underlying sequence. The
/// following example uses the `zip(_:_:_:_:)` function to iterate over an array
/// of strings, an array of bolean values, an array of integer values, and a countable range, at the same time:
///
///     let evens = [false, true, false, true)
///     let squares = [1, 4, 9, 16]
///     let words = ["one", "two", "three", "four"]
///     let numbers = 1...4
///
///     for (word, number, even, square) in zip(words, numbers, evens, squares) {
///         print("\(word): \(number) --> \(number) is \(even ? "even" : "odd") and \(number)² = \(square)")
///     }
///     // Prints "one: 1 --> 1 is odd and 1² = 1"
///     // Prints "two: 2 --> 2 is even and 2² = 4"
///     // Prints "three: 3 --> 3 is odd and 3² = 9"
///     // Prints "four: 4 --> 4 is even and 4² = 16"
///
/// If the four sequences passed to `zip(_:_:_:_:)` are different lengths, the
/// resulting sequence is the same length as the shorter sequence. In this
/// example, the resulting array is the same length as `words`, `evens`, `and `squares`:
///
///     let naturalNumbers = 1...Int.max
///     let zipped = Array(zip(words, naturalNumbers, evens, squares))
///     // zipped == [("one", 1, false, 1), ("two", 2, true, 4), ("three", 3, false, 9), ("four", 4, true, 16)]
///
/// - Parameters:
///   - sequence1: The first sequence or collection to zip.
///   - sequence2: The second sequence or collection to zip.
///   - sequence3: The third sequence or collection to zip.
///   - sequence4: The fourth sequence or collection to zip.
/// - Returns: A sequence of tuple pairs, where the elements of each pair are
///   corresponding elements of `sequence1`, `sequence2`, `sequence3`, and `sequence3`.
public func zip<Sequence1, Sequence2, Sequence3, Sequence4>(
    _ sequence1: Sequence1, _ sequence2: Sequence2, _ sequence3: Sequence3, _ sequence4: Sequence4
    ) -> Zip4Sequence<Sequence1, Sequence2, Sequence3, Sequence4> {
    return Zip4Sequence(_sequence1: sequence1, _sequence2: sequence2, _sequence3: sequence3, _sequence4: sequence4)
}

/// A sequence of pairs built out of four underlying sequences.
///
/// In a `Zip4Sequence` instance, the elements of the *i*th pair are the *i*th
/// elements of each underlying sequence. To create a `Zip4Sequence` instance,
/// use the `zip(_:_:_:_:)` function.
///
/// The following example uses the `zip(_:_:_:_:)` function to iterate over an array
/// of strings, an array of bolean values, an array of integer values, and a countable range, at the same time:
////
///     let evens = [false, true, false, true)
///     let squares = [1, 4, 9, 16]
///     let words = ["one", "two", "three", "four"]
///     let numbers = 1...4
///
///     for (word, number, even, square) in zip(words, numbers, evens, squares) {
///         print("\(word): \(number) --> \(number) is \(even ? "even" : "odd") and \(number)² = \(square)")
///     }
///     // Prints "one: 1 --> 1 is odd and 1² = 1"
///     // Prints "two: 2 --> 2 is even and 2² = 4"
///     // Prints "three: 3 --> 3 is odd and 3² = 9"
///     // Prints "four: 4 --> 4 is even and 4² = 16"
@_fixed_layout // FIXME(sil-serialize-all)
public struct Zip4Sequence<Sequence1 : Sequence, Sequence2 : Sequence, Sequence3 : Sequence, Sequence4 : Sequence> {
    internal let _sequence1: Sequence1
    internal let _sequence2: Sequence2
    internal let _sequence3: Sequence3
    internal let _sequence4: Sequence4

    @available(*, deprecated, renamed: "Sequence1.Iterator")
    public typealias Stream1 = Sequence1.Iterator
    @available(*, deprecated, renamed: "Sequence2.Iterator")
    public typealias Stream2 = Sequence2.Iterator
    @available(*, deprecated, renamed: "Sequence3.Iterator")
    public typealias Stream3 = Sequence3.Iterator
    @available(*, deprecated, renamed: "Sequence4.Iterator")
    public typealias Stream4 = Sequence4.Iterator

    /// Creates an instance that makes pairs of elements from `sequence1` and
    /// `sequence2`.
    public // @testable
    init(_sequence1 sequence1: Sequence1, _sequence2 sequence2: Sequence2, _sequence3 sequence3: Sequence3, _sequence4 sequence4: Sequence4) {
        (_sequence1, _sequence2, _sequence3, _sequence4) = (sequence1, sequence2, sequence3, sequence4)
    }
}

extension Zip4Sequence {
    /// An iterator for `Zip4Sequence`.
    @_fixed_layout // FIXME(sil-serialize-all)
    public struct Iterator {
        internal var _baseStream1: Sequence1.Iterator
        internal var _baseStream2: Sequence2.Iterator
        internal var _baseStream3: Sequence3.Iterator
        internal var _baseStream4: Sequence4.Iterator
        internal var _reachedEnd: Bool = false

        /// Creates an instance around a set of 4 underlying iterators.
        internal init(
            _ iterator1: Sequence1.Iterator,
            _ iterator2: Sequence2.Iterator,
            _ iterator3: Sequence3.Iterator,
            _ iterator4: Sequence4.Iterator
            ) {
            (_baseStream1, _baseStream2, _baseStream3, _baseStream4) = (iterator1, iterator2, iterator3, iterator4)
        }
    }
}

extension Zip4Sequence.Iterator: IteratorProtocol {
    /// The type of element returned by `next()`.
    public typealias Element = (Sequence1.Element, Sequence2.Element, Sequence3.Element, Sequence4.Element)

    /// Advances to the next element and returns it, or `nil` if no next element
    /// exists.
    ///
    /// Once `nil` has been returned, all subsequent calls return `nil`.
    public mutating func next() -> Element? {
        // The next() function needs to track if it has reached the end. If we
        // didn't, and the first sequence is longer than the second, third, and
        // fourth, then when we have already exhausted the second, third, and
        // fourth sequence, on every subsequent call to next() we would consume
        // and discard one additional element from the first sequence, even
        // though next() had already returned nil.
        if _reachedEnd {
            return nil
        }

        guard let element1 = _baseStream1.next(),
            let element2 = _baseStream2.next(),
            let element3 = _baseStream3.next(),
            let element4 = _baseStream4.next()
            else {
                _reachedEnd = true
                return nil
        }

        return (element1, element2, element3, element4)
    }
}

extension Zip4Sequence: Sequence {
    public typealias Element = (Sequence1.Element, Sequence2.Element, Sequence3.Element, Sequence4.Element)

    /// Returns an iterator over the elements of this sequence.
    public func makeIterator() -> Iterator {
        return Iterator(
            _sequence1.makeIterator(),
            _sequence2.makeIterator(),
            _sequence3.makeIterator(),
            _sequence4.makeIterator())
    }
}

@available(*, deprecated, renamed: "Zip4Sequence.Iterator")
public typealias Zip4Iterator<T, U, V, W> = Zip4Sequence<T, U, V, W>.Iterator where T: Sequence, U: Sequence, V: Sequence, W: Sequence

Caveat: This overload only is for a fixed number of inputs whereas I am ideally looking for an zip function that accepts an indiscriminate number of AnySequence inputs.


Just to reiterate, I am looking to create a zip(_: AnySequence...) function that I can use to zip any number of sequences through the use of a variadic parameter.

Thank you everyone in advance for all of your help, I really appreciate it!



回答1:


Sadly, this is not completely possible in Swift since we can't have different Array types for Variadic parameters. A possible work around would be to convert all arrays to type [Any], and then zip them all together which forms an array array, and than using map, converting the arrays to the wanted tuple using force casting. The solution might be bit dirty but not all too complicated.

extension Sequence {

    //easy conversion of any sequence to [Any]
    var untyped:[Any] {
        return self as! [Any]
   }
}

func zip(_ untypedSequences:[Any]...) -> [[Any]] {
    let count = untypedSequences.map{$0.count}.min() ?? 0
    return (0..<count).map{ index in untypedSequences.map{ $0[index] } }
}

//Test
let test1 = ["a", "b", "c"]
let test2 = [1, 2, 3, 4]
let test3 = [7.2, 12.23, 1.3]

let zippedTests = zip(test1.untyped, test2.untyped, test3.untyped).map {
    ($0[0] as! String, $0[1] as! Int, $0[2] as! Double)
}



回答2:


You need all the functions but you don't need multiple ZipSequence types.

@inlinable public func zip<Sequence0: Sequence, Sequence1: Sequence, Sequence2: Sequence>(
  _ sequence0: Sequence0, _ sequence1: Sequence1, _ sequence2: Sequence2
) -> AnySequence<(Sequence0.Element, Sequence1.Element, Sequence2.Element)> {
  .init(
    sequence(
      state: (
        sequence0.makeIterator(), sequence1.makeIterator(), sequence2.makeIterator()
      )
    ) {
      .init(
        ($0.0.next(), $0.1.next(), $0.2.next())
      )
    }
  )
}

@inlinable public func zip<
  Sequence0: Sequence, Sequence1: Sequence, Sequence2: Sequence, Sequence3: Sequence
>(
  _ sequence0: Sequence0, _ sequence1: Sequence1, _ sequence2: Sequence2, _ sequence3: Sequence3
) -> AnySequence<(Sequence0.Element, Sequence1.Element, Sequence2.Element, Sequence3.Element)> {
  .init(
    sequence(
      state: (
        sequence0.makeIterator(), sequence1.makeIterator(), sequence2.makeIterator(), sequence3.makeIterator()
      )
    ) {
      .init(
        ($0.0.next(), $0.1.next(), $0.2.next(), $0.3.next())
      )
    }
  )
}
public extension Optional {
  /// Exchange three optionals for a single optional tuple.
  /// - Returns: `nil` if any tuple element is `nil`.
  init<Wrapped0, Wrapped1, Wrapped2>(_ optionals: (Wrapped0?, Wrapped1?, Wrapped2?))
  where Wrapped == (Wrapped0, Wrapped1, Wrapped2) {
    switch optionals {
    case let (wrapped0?, wrapped1?, wrapped2?):
      self = (wrapped0, wrapped1, wrapped2)
    default:
      self = nil
    }
  }

  /// Exchange four optionals for a single optional tuple.
  /// - Returns: `nil` if any tuple element is `nil`.
  init<Wrapped0, Wrapped1, Wrapped2, Wrapped3>(_ optionals: (Wrapped0?, Wrapped1?, Wrapped2?, Wrapped3?))
  where Wrapped == (Wrapped0, Wrapped1, Wrapped2, Wrapped3) {
    switch optionals {
    case let (wrapped0?, wrapped1?, wrapped2?, wrapped3?):
      self = (wrapped0, wrapped1, wrapped2, wrapped3)
    default:
      self = nil
    }
  }
}


来源:https://stackoverflow.com/questions/50723876/variadic-zip-function-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!