I have three arrays I am trying to run through and I want to use the values from all three arrays in one function. This might sound confusing but here is what I have:
var Name = [Joe, Sarah, Chad]
var Age = [18, 20, 22]
var Gender = [Male, Female, Male]
for name in Name {
for age in Age {
for gender in Gender {
makeUser(name, userAge: age, userGender: gender)
}
}
}
This runs but what I get is: (makeUser prints out the 3 values)
Joe, 18, Male
Joe, 20, Male
Joe, 22, Male
Joe, 18, Female
Joe, 20, Female
Joe, 22, Female ....
And so on.
All I want is
Joe, 18, Male
Sarah, 20, Female
Chad, 22, Male
Is this possible? Any help is appreciated.
Thanks!
This is a very common requirement so the standard library caters to it with a function, zip
:*
for (a,b) in zip(seq1, seq2) {
// a and b will be matching pairs from the two sequences
}
Unfortunately, as of right now, zip
only does pairs, even though in theory it could be overloaded to do triples. However, it’s not a big deal, you can just nest them:
var names = ["Joe", "Sarah", "Chad"]
var ages = [18, 20, 22]
var genders: [Gender] = [.Male, .Female, .Male]
for (name,(age,gender)) in zip(names,zip(ages,genders)) {
makeUser(name, userAge: age, userGender: gender)
}
Note, it will only serve up to the shortest sequence, so if there are more names than ages or genders, you’ll only get the matching names.
This might seem like a down side compared to using an index, and this might also seem more complex, but the alternative’s simplicity is deceptive. Bear in mind what would happen if you used indices
or enumerate
alongside arrays that didn’t match – you’d get an array out of bounds assertion (or you’d have to put in checking logic).
zip
avoids this problem. It also means you can use sequences instead of collections, as well as working with collections that don’t have integer indices (unlike enumerate
) or collections that have different index types (e.g. a String
and an Array
).
*(in the current beta, anyway – zip
returns a Zip2
object. In Swift 1.1, you need to create the Zip2
version directly as zip
has only just been introduced)
If you are always sure the arrays will be equal in length, then you are better to just loop through one of the arrays and use it's index to reference the others:
for (index, name) in enumerate(Name) {
makeUser(name, userAge: Age[index], userGender: Gender[index])
}
However, I would recommend getting this data into a dictionary, but I assume this is just sample data to illustrate a point. :)
You could use a custom zip3
function, which is not hard to write.
struct Zip3Sequence<E1, E2, E3>: Sequence, IteratorProtocol {
private let _next: () -> (E1, E2, E3)?
init<S1: Sequence, S2: Sequence, S3: Sequence>(_ s1: S1, _ s2: S2, _ s3: S3) where S1.Element == E1, S2.Element == E2, S3.Element == E3 {
var it1 = s1.makeIterator()
var it2 = s2.makeIterator()
var it3 = s3.makeIterator()
_next = {
guard let e1 = it1.next(), let e2 = it2.next(), let e3 = it3.next() else { return nil }
return (e1, e2, e3)
}
}
mutating func next() -> (E1, E2, E3)? {
return _next()
}
}
func zip3<S1: Sequence, S2: Sequence, S3: Sequence>(_ s1: S1, _ s2: S2, _ s3: S3) -> Zip3Sequence<S1.Element, S2.Element, S3.Element> {
return Zip3Sequence(s1, s2, s3)
}
let names = ["Joe", "Sarah", "Chad"]
let ages = [18, 20, 22]
let genders = ["Male", "Female", "Male"]
for (name, age, gender) in zip3(names, ages, genders) {
print("Name: \(name), age: \(age), gender: \(gender)")
}
The above code prints:
Name: Joe, age: 18, gender: Male
Name: Sarah, age: 20, gender: Female
Name: Chad, age: 22, gender: Male
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With