I've run into a situation that I'm sure is not that uncommon. I have two arrays of objects that conform to a protocol and I want to check if they are the equal.
What I'd really like to do is this:
protocol Pattern: Equatable
{
func isEqualTo(other: Pattern) -> Bool
}
func ==(rhs:Pattern, lhs:Pattern) -> Bool
{
return rhs.isEqualTo(lhs)
}
extension Equatable where Self : Pattern
{
func isEqualTo(other: Pattern) -> Bool
{
guard let o = other as? Self else { return false }
return self == o
}
}
However, this leads to the compile error:
Error:(10, 30) protocol 'Pattern' can only be used as a generic constraint because it has Self or associated type requirements
Based on this post I realise that I need to lose the Equatable inheritance on my protocol and push it down onto the concrete 'Pattern' declarations. Though I really don't understand why. If I'm defining how the two objects are equal based on the protocol by overloading == there really is no issue as far as I can see. I don't even need to know the actual types or whether they are classes or structs.
Regardless, this is all well and good and I can now compare concretePattern.isEqualTo(otherConcretePattern)
but the issue remains that I can no longer compare arrays of these objects like I can compare an array of a concrete type as array equality relies on overloading the == operator.
The best I've managed to do so far is glom an isEqualTo
method onto CollectionType
via an extension. This at least allows me to compare arrays. But frankly, this code stinks.
extension CollectionType where Generator.Element == Pattern
{
func isEqualTo(patterns:[Pattern]) -> Bool {
return self.count as? Int == patterns.count && !zip(self, patterns).contains { !$0.isEqualTo($1) }
}
}
Is there really no other way of doing this? Please tell me I'm missing something obvious.
When should your Swift types be equatable or comparable? “Equatable” relates to being equal, and “comparable” relates to the comparison between objects. This is important, because how can we be certain that two complex objects are the same?
Sponsor sarunw.com and reach thousands of iOS developers. In Swift, you can check for string and character equality with the "equal to" operator ( ==) and "not equal to" operator ( != ). We use the same operator for Character. These "equal to" operator is suitable for simple string comparison where you required an exact match.
With the introduction of the Identifiable protocol, however, value types in Swift can also be used to model entities with a stable identity, while controlling the duration and scope of the identity according to the needs of the application.
If you want to experiment doing the same with a Swift Class, the process is similar. If you wish to declare equality with more than one property, you can use the && operator, and if you wish to sort more than one property, you can use the || operator. I know it’s animals, but animals are nice, right?
I have two arrays of objects that conform to a protocol and I want to check if they are the equal.
So you want to say the two arrays are equal if all the elements in them are equal and the elements all conform to pattern. i.e.
If a, b, c and d are all things that conform to Pattern, you want
a == c
a != b
a != d
b != d
let array1: [Pattern] = [a, b, c]
let array2: [Pattern] = [a, b, a]
let array3: [Pattern] = [a, d, c]
array1 == array2 // true
array1 == array3 // false
The easiest way to do this is actually to define an equality operator for two arrays of patterns i.e.
protocol Pattern
{
func isEqualTo(other: Pattern) -> Bool
}
func ==(rhs: Pattern, lhs: Pattern) -> Bool
{
return rhs.isEqualTo(lhs)
}
func ==(lhs: [Pattern], rhs: [Pattern]) -> Bool
{
guard lhs.count == rhs.count else { return false }
var i1 = lhs.generate()
var i2 = rhs.generate()
var isEqual = true
while let e1 = i1.next(), e2 = i2.next() where isEqual
{
isEqual = e1 == e2
}
return isEqual
}
I defined two types that conform to Pattern and tried various equality compares and it all works
struct Foo: Pattern
{
let data: String
init(data: String)
{
self.data = data
}
func isEqualTo(other: Pattern) -> Bool
{
guard let other = other as? Foo else { return false }
return self.data == other.data
}
}
struct Bar: Pattern
{
let data: String
init(data: String)
{
self.data = data
}
func isEqualTo(other: Pattern) -> Bool
{
guard let other = other as? Bar else { return false }
return self.data == other.data
}
}
let a = Foo(data: "jeremyp")
let b = Bar(data: "jeremyp")
let c = Foo(data: "jeremyp")
let d = Foo(data: "jeremy")
let comp1 = a == c // true
let comp2 = a == b // false
let comp3 = a == d // false
let array1: [Pattern] = [a, b, c]
let array2: [Pattern] = [a, b, a]
let array3: [Pattern] = [a, d, c]
let comp4 = array1 == array2 // true
let comp5 = array1 == array3 // false
The Swift answer:
protocol _Pattern
{
func _isEqualTo(_other: Any) -> Bool?
}
extension _Pattern where Self: Pattern
{
func _isEqualTo(_other: Any) -> Bool?
{
return (_other as? Self).map({ self.isEqualTo($0) })
}
}
protocol Pattern: _Pattern, Equatable
{
func isEqualTo(other: Self) -> Bool
}
extension Pattern
{
func isEqualTo(other: _Pattern) -> Bool
{
return _isEqualTo(other) ?? false
}
}
func == <T: Pattern>(rhs: T, lhs: T) -> Bool
{
return rhs.isEqualTo(lhs)
}
This is a pattern (pun intended) that I developed myself, and it works extremely well for situations like these. _Pattern
is an auto-implemented protocol courtesy of the new protocol-extension feature, and represents a type-erased version of Pattern
.
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