Swift Tuple index using a variable as the index?
Asked Answered
S

5

6

Swift Tuple index using a variable as the index? Anyone know if it is possible to use a variable as the index for a Swift tuple index. I wish to select and item from a tuple using a random number. I have the random number as a variable but cannot see how to use that as the index for a tuple. I have searched various places already

Swaraj answered 28/6, 2016 at 11:29 Comment(5)
A tuple can be "inhomogeneous", e.g. (Int, String, Double, [Float]). Why don't you use an array?Selfsown
In case you really want a tuple: you should be able to make use of runtime introspection to inspect the children of the tuple (mirr = Mirror(reflecting: yourTuple)), and choose a child randomly (quite a roundabout way w.r.t. to just using an array, though).Cleon
Why don't you use a dictionary?Liquidate
I think the PO wants to set keys to access the tuple elements (Swift Tuple index using a variable as the index), so it's rather a dictionary than an arrayLiquidate
Compare Swift: Access tuples by subscript – some answers need to be updated to the current Swift though.Selfsown
C
6

Make sure you've chosen the correct data structure

If you've reached a point where you need to access tuple members as if "indexed", you should probably look over your data structure to see if a tuple is really the right choice for you in this case. As MartinR mentions in a comment, perhaps an array would be a more appropriate choice, or, as mentioned by simpleBob, a dictionary.

Technically: yes, tuple members can be accessed by index-style

You can make use of runtime introspection to access the children of a mirror representation of the tuple, and choose a child value given a supplied index.

E.g., for 5-tuples where all elements are of same type:

func getMemberOfFiveTuple<T>(tuple: (T, T, T, T, T), atIndex: Int) -> T? {
    let children = Mirror(reflecting: tup).children
    guard case let idx = IntMax(atIndex)
        where idx < children.count else { return nil }
    
    return children[children.startIndex.advancedBy(idx)].value as? T
}

let tup = (10, 11, 12, 13, 14)
let idx = 3

if let member = getMemberOfFiveTuple(tup, atIndex: idx) {
    print(member)
} // 13

Note that the type of child.value is Any, hence we need to perform an attempted type conversion back to the element type of the tuple. In case your tuple contains different-type members, the return type cannot be known at compile time, and you'd probably have to keep using type Any for the returned element.

Cleon answered 28/6, 2016 at 11:44 Comment(0)
D
3

So, what people here are saying - don't use a tuple if you don't actually need one - is correct. However, there is one caveat.

In my experience, using a Swift Array with a static size in performance critical code actually slows down your code by quite a bit. Take a look:

let clz_lookup = [4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
func clz(x : UInt32) -> Int {
    guard x != 0 else { return 32 }
    var x = x
    var n = 0

    if (x & 0xFFFF0000) == 0 { n  = 16; x <<= 16 } else { n = 0 }
    if (x & 0xFF000000) == 0 { n +=  8; x <<=  8 }
    if (x & 0xF0000000) == 0 { n +=  4; x <<=  4 }

    return n + clz_lookup[Int(x >> (32 - 4))]
}

This is a fairly straight forward piece of code that uses a cached lookup table at the end. However, if we profile this using instruments we'll see that the lookup is actually much slower than the if statements it replaces! My guess is the reason for that is probably bounds checking or something similar, plus that there is an additional pointer indirection in the implementation.

In any case, that's not good, and since the above is a function that is potentially being called a lot we want to optimise it. A simple technique for that is creating a Tuple instead of an Array, and then simply using withUnsafeMutablePointer to get a pointer directly into the Tuple (which is laid out as a contiguous, static array in memory - exactly what we want in this case!):

var clz_table = (4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0)
let clz_lookup = withUnsafeMutablePointer(to: &clz_table) { $0.withMemoryRebound(to: Int.self, capacity: 15) { $0 } }

This can be used exactly like a regular C-style array in that you can index into it and even change the values at particular indices, but there is no way to grow this array. This method of indexing should be much faster than the other solutions that are mentioning reflection, but is potentially unsafe if used wrong.

Diluvium answered 24/7, 2017 at 12:49 Comment(1)
The technique you've shown is especially useful when importing C arrays in Swift, since they sometimes get declared as tuples instead of (mutable) pointers.Byelorussian
E
1

Reference: https://gist.github.com/ctreffs/785db636d68a211b25c989644b13f301

In Swift 5:

func tupleToArray<Tuple, Value>(tuple: Tuple) -> [Value] {
    let tupleMirror = Mirror(reflecting: tuple)
    assert(tupleMirror.displayStyle == .tuple, "Given argument is no tuple")
    assert(tupleMirror.superclassMirror == nil, "Given tuple argument must not have a superclass (is: \(tupleMirror.superclassMirror!)")
    assert(!tupleMirror.children.isEmpty, "Given tuple argument has no value elements")
    return tupleMirror.children.compactMap { (child: Mirror.Child) -> Value? in
        let valueMirror = Mirror(reflecting: child.value)
        assert(valueMirror.subjectType == Value.self, "Given tuple argument's child type (\(valueMirror.subjectType)) does not reflect expected return value type (\(Value.self))")
        return child.value as? Value
    }
}

let sss: [String] = tupleToArray(tuple: ("📷", "🍉🍉", "✈️✈️✈️" ,"🍎🍎🍎"))
print(sss)
Expansion answered 27/6, 2020 at 12:28 Comment(0)
D
0

You can cast a tuple to a buffer only if it has an homogeneous type. But before doing so, wonder if an array can't do the job.

var tuple: (Int, Int, Int, Int) = (0,1,2,3)

withUnsafeMutablePointer(to: &tuple) { pointer in
    pointer.withMemoryRebound(to: Int.self, capacity: 4) { buffer in
        buffer[3] = 0
    }
}

print(tuple)

result:

(0, 1, 2, 0)
Diastasis answered 29/12, 2020 at 23:41 Comment(0)
A
-2

As long as tuple index is just default name, and you can provide your own names

let testTuple = (first: 1, second: 2.0)
let first: Int = testTuple.first
let second: Double = testTuple.second

you can not use variable as index. Pretty close question "can I use KVC for tuple?", and answer - you can't

Axe answered 28/6, 2016 at 11:36 Comment(1)
Thanks everyone, the answer seems to be don't use Tuples. cheers MikeSwaraj

© 2022 - 2024 — McMap. All rights reserved.