Reputation: 2932
I use zip
on two arrays of tuples. When I iterate the result, the result tuple contains tuples from the left-hand side only.
Interestingly, Array(zip(...))
produces a collection as expected. I want to save a few cycles and memory and prefer not to generate a new array just for the sake of looping.
let expectation: [(String, UInt)] = [("bar", 0)]
let comparison: [(String, Int)] = [("foo", 0)]
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
for (l, r) in zip(lhs, rhs) {
// Looking at `l` and `r` in lldb shows both are the same.
if l.0 != r.0 || Int(l.1) != r.1 {
return false
}
}
return true
}
let equals = (expectation == comparison) // true?!?!
This was meant to be a convenience method to compare records of function calls in a test double to test data from the actual test cases. The double records (String, UInt)
, typing tuples in test cases produces (String, Int)
, so I thought: let's create an easy equality function! Changing UInt
to Int
doesn't change a thing.
How's that? Renders zip pretty useless to me (except when you can explain what's going on).
Upvotes: 2
Views: 157
Reputation: 10091
Yeah, looks like a bug. Weirdly, though, if you replace the for loop with contains(), you don't need to deconstruct the tuple, and it works like you'd expect:
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
return !contains(zip(lhs, rhs)) {
l, r in l.0 != r.0 || Int(l.1) != r.1
}
}
Upvotes: 1
Reputation: 40965
Can’t decide whether this is a bug or just something I’m not understanding (suspect bug but need to play with it more).
However, here’s a workaround in the mean-time, which involves fully destructuring the data:
let expectation: [(String, UInt)] = [("bar", 0)]
let comparison: [(String, Int)] = [("foo", 1)]
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
for ((ls, li),(rs,ri)) in zip(lhs, rhs) {
// Looking at `l` and `r` in lldb shows both are the same.
if ls != rs || Int(li) != ri {
return false
}
}
return true
}
let equals = (expectation == comparison) // now returns false
In theory, this ought to be more easily written as:
equal(expectation, comparison) {
$0.0 == $1.0 && Int($0.1) == $1.1
}
except that, infuriatingly, the equal
function that takes a predicate still requires the elements of the two sequences to be the same! Radar 17590938.
A quick fix for this specific to arrays could look like:
func equal<T,U>(lhs: [T], rhs: [U], isEquivalent: (T,U)->Bool) -> Bool {
if lhs.count != rhs.count { return false }
return !contains(zip(lhs, rhs)) { !isEquivalent($0) }
}
// now the above use of equal will compile and return the correct result
p.s. you might want to add an overflow check for the UInt
conversion
Upvotes: 3
Reputation: 6726
In support to @Airspeed Velocity, it seems a bug since you can define only the first tuple, than the second will work as expected :-/ Xcode 6.3.2 Swift 1.2
let expectation: [(String, UInt)] = [("bar", 0)]
let comparison: [(String, Int)] = [("foo", 1)]
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
for ((l0, l1), r) in zip(lhs, rhs) {
// Explicitly detiled first tuple
if l0 != r.0 || Int(l1) != r.1 {
return false
}
}
return true
}
let equals = (expectation == comparison) // false as expected
Upvotes: 2