Any way to iterate a tuple in swift?

后端 未结 6 661
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-04 15:24

I am curious how to do a for loop with a tuple in swift.

I know that to access each member you can use dot notation using the index number

var tuple         


        
相关标签:
6条回答
  • 2020-12-04 15:59

    Swift does not currently support iterating over tuples.

    The biggest reasons are:

    1. There is no way at runtime to determine the number of elements in a tuple
    2. There is no way to access an element at a specific index except for the compile time accessors like tupleList.0. You would really want a subscript tupleList[0] but that is not provided to us

    Frankly, I can't see a reason that you would use a tuple instead of an Array if you want to iterate over it.

    It doesn't make sense to iterate over a tuple because:

    1. Tuples always have a fixed length and each element has a fixed type
    2. You can name each tuple member with a name you can use to access it later

    Arrays are well made to iterate over:

    1. Arbitrary length
    2. Can store multiple types using a common superclass or AnyObject
    3. Can be declared as a literal in a similar fashion to tuples: var list = ["A",2.9,3,8,5,6,7,8,9]
    0 讨论(0)
  • 2020-12-04 16:04

    You can using reflection Swift 5

    Try this in a Playground:

    let tuple = (1, 2, "3")
    let tupleMirror = Mirror(reflecting: tuple)
    let tupleElements = tupleMirror.children.map({ $0.value })
    tupleElements
    

    Output:

    0 讨论(0)
  • 2020-12-04 16:07

    @dankogai's excellent solution, updated for Swift 3.0:

    func iterate<Tuple>(_ tuple:Tuple, body:(_ label:String?,_ value:Any)->Void) {
        for child in Mirror(reflecting: tuple).children {
            body(child.label, child.value)
        }
    }
    

    Usage remains identical to @dankogai's examples (beyond Swift 2's println()print() rename).

    Note that the label is now of type String? when it was formerly String, to match the type change from Swift 1's MirrorType.subscript(…).0 to Swift 3's Mirror.Child.label.  However, for labelless tuples the label arg comes back as ".0", ".1", ".2", etc.— it's only nil for some other types.

    Also, I took the liberty of renaming types & args to better match Swift 3's solidified naming standards, and changing the closure return type to Void.

    Sidenote: I noticed somebody downvoted me here— I can't imagine why, other than the (fair) argument that building app functionality around reflection in Swift is hacking the type system, and is likely to lead to crappy code throughout (Swift's tuples shouldn't be considered an abstract data type, but rather a small collection of variables, akin to method args).  As a counter-argument, I originally ended up porting this to Swift 3 in project because I needed it— for better descriptions and debugDescriptions.  Because sane debug output will saves you hours and hours of frustration. ;-)  Additionally, this could be really useful for unit tests… because tests are ultimately most interested in “did the result of this operation match what we expect?”

    0 讨论(0)
  • 2020-12-04 16:10

    Yes, you can!

    func iterate<C,R>(t:C, block:(String,Any)->R) {
        let mirror = reflect(t)
        for i in 0..<mirror.count {
            block(mirror[i].0, mirror[i].1.value)
        }
    }
    

    And voila!

    let tuple = ((false, true), 42, 42.195, "42.195km")
    iterate(tuple) { println("\($0) => \($1)") }
    iterate(tuple.0){ println("\($0) => \($1)")}
    iterate(tuple.0.0) { println("\($0) => \($1)")} // no-op
    

    Note the last one is not a tuple so nothing happens (though it is a 1-tuple or "Single" which content can be accessed .0, reflect(it).count is 0).

    What's interesting is that iterate() can iterate even other types of collection.

    iterate([0,1])              { println("\($0) => \($1)") }
    iterate(["zero":0,"one":1]) { println("\($0) => \($1)") }
    

    And that collection includes class and struct!

    struct Point { var x = 0.0, y = 0.0 }
    class  Rect  { var tl = Point(), br = Point() }
    iterate(Point()) { println("\($0) => \($1)") }
    iterate(Rect())  { println("\($0) => \($1)") }
    

    Caveat: the value passed as the 2nd argument of the block is type Any. You have to cast it back to the values with original type.

    0 讨论(0)
  • 2020-12-04 16:17

    No, you can't. The reason is that tuple items are not all required to have the same type, so you would not be able to know what type each should have.

    0 讨论(0)
  • 2020-12-04 16:20

    Details

    • Xcode 11.2.1 (11B500), Swift 5.1

    Base Solution

    struct Tuple<T> {
        let original: T
        private let array: [Mirror.Child]
        init(_ value: T) {
            self.original = value
            array = Array(Mirror(reflecting: original).children)
        }
        func forEach(closure: (Mirror.Child) -> Void) { array.forEach { closure($0) } }
        func getOnlyValues<T: Any>() -> [T] { array.compactMap { $0.value as? T } }
        func getAllValues() -> [Any] { array.compactMap { $0.value } }
    }
    

    Usage on base solution

    let num: Int? = 3
    let str: String? = nil
    let x = (1, "stew", str, 5.4, 2, num)
    
    let tuple = Tuple(x)
    tuple.forEach { print("\($0)") }
    
    print("\(tuple.getAllValues())")            // [1, "stew", nil, 5.4, 2, Optional(3)]
    print("\(tuple.getOnlyValues() as [Int])")  // [1, 2, 3]
    

    More sugar

    func valuesFrom<V>(tuple: V) -> [Any] { return Tuple(tuple).getAllValues() }
    func onlyValuesFrom<T,V>(tuple: V) -> [T] { return Tuple(tuple).getOnlyValues() as [T] }
    
    print(valuesFrom(tuple: x))                 // [1, "stew", nil, 5.4, 2, Optional(3)]
    print(onlyValuesFrom(tuple: x) as [Int])    // [1, 2, 3]
    
    0 讨论(0)
提交回复
热议问题