Any way to iterate a tuple in swift?
Asked Answered
A

6

54

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 tupleList = ("A",2.9,3,8,5,6,7,8,9)

for each in tupleList {
    println(each)
}

//Error: Type does not conform to protocol sequence

Arborescent answered 19/6, 2014 at 4:56 Comment(5)
Why use a tuple instead of an array if you want to iterate over it?Amitie
It seems, for me at least, that fixed-sized arrays imported from a C header becomes tuples. This, however, could be a side-effect of not having arrays with a fixed size.Euphemia
Let arrays are fixed immutable now. I think that was back on beta 3Muskrat
The problem still is that fixed C arrays can be mutable.Euphemia
@Amitie Perhaps you want to go through a tuple's elements and normalize them. You could use an array, but your code is much cleaner expecting a fixed-size tuple and specific element labels. You could also write a normalization func that's specialized to your fixed-size tuple, but in the spirit of keeping your code DRY, you'd really like a generalized solution.Cervelat
C
47

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.

Commissariat answered 18/7, 2014 at 18:34 Comment(3)
In swift 2 reflect(t) is replaced by Mirror(reflecting: t) but this documented as a playground/debugging feature only.Briard
Jafar, I don't think it's implied by the documentation that mirrors are to be used by playgrounds and debugger only. What is specifically stated in the documentation is "Mirrors are used by playgrounds and the debugger."Maxson
Doing this works just fine in Swift 4 with a bit of change in syntaxHyaluronidase
H
25

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:

enter image description here

Hyaluronidase answered 19/5, 2018 at 11:22 Comment(1)
Consider adding print(tupleElements) for non-playgroundCountertype
A
12

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]
Amitie answered 19/6, 2014 at 5:25 Comment(4)
I was thinking to iterate over a tuple that came out of a function.Muskrat
you can name each member of the type in result of a function and access each element by name. A tuple is meant to be a cheap struct that you don't have to predefine a type for. You can't iterate over the members of a struct either. (at least not until / if they add reflection to Swift)Amitie
You cannot substitute a tuple for an array if your members are not of the same type, for example.Leninist
I was also stuck with a same situation, suppose I have two sides "left" and "right". So I will prefer making a tuple like let side = (left:"left", right:"right") so that I can access them by name to make it error free. And say I have 10 category of objects with left and right type in each category, so I want to iterate over each category. How can I? I won't use array since for some case I want to use things like side.leftLoden
C
9

@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.

Cervelat answered 19/9, 2016 at 3:17 Comment(0)
T
2

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]
Transient answered 15/11, 2019 at 3:23 Comment(0)
A
-2

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.

Africah answered 19/6, 2014 at 5:0 Comment(4)
That's as valid an arbitrary rationale as any I've heard at least :) To completely buy it, however, would mean you couldn't iterate an NSArray :)Vicky
Any and AnyObject can be used to represent anything. That is what is used in NSArray or any other translations of id from Objective-CAmitie
Except function types, for some reason: "Any can represent an instance of any type at all, apart from function types." They should fix that.Credits
@Credits I disagree. Any can represent any data type, which is logically distinct in OO languages like Swift. Function refs are a different beast in OO— they're meant to be called directly or bound to statically (compile-time), not passed around in collections or transformed dynamically (which would require runtime interpretation). That said, many plausible “Any-function” use-cases can be solved in Swift using generics.Cervelat

© 2022 - 2024 — McMap. All rights reserved.