arraysswiftprotocolsswift2heterogeneous

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.


Solution

  • 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