Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compare two arrays of protocols for equality in Swift?

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.

like image 278
Daniel Wood Avatar asked Oct 15 '15 11:10

Daniel Wood


People also ask

When should your swift types be equatable or comparable?

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?

How to check for string and character equality in Swift?

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.

What are value types in Swift and how to use them?

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.

How do you declare equality with multiple properties in Swift?

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?


2 Answers

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
like image 51
JeremyP Avatar answered Oct 23 '22 14:10

JeremyP


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.

like image 28
Vatsal Manot Avatar answered Oct 23 '22 14:10

Vatsal Manot