6

I have two dictionaries that work as text attributes of type [String: Any] and in order to toggle ON or OFF the desired attributes I need to check if two dictionaries are the same.

I tried the following:

let current = inputTextView.typingAttributes

let undo = current.elementsEqual(attributes, by: { (arg0, arg1) -> Bool in
    return ((arg0.key == arg1.key) && (arg0.value == arg1.value))
})

But on the second evaluation I get the error:

Binary operator '==' cannot be applied to two 'Any' operands

What's the best approach here to compare two dictionaries of type [String: Any] ??

Thank you

Ivan Cantarino
  • 3,058
  • 4
  • 34
  • 73
  • what is the expected outcome? Why use `Any`? You may be able to use `===` - or you have to ensure that every type in the dictionary is Equatable. – luk2302 Dec 04 '17 at 11:26
  • 2
    You can't compare objects of type `Any`. Swift does not know what `==` for any should mean. You have to tell it. – dasdom Dec 04 '17 at 11:26
  • The `Any` is needed because it is a `typingAttributes` to a `UITextView` which takes in the `[String: Any]` – Ivan Cantarino Dec 04 '17 at 11:27
  • `var typingAttributes: [String : Any] { get set }` This is what I need to compare -- it is what `UITextView` takes by definition as its typing attributes – Ivan Cantarino Dec 04 '17 at 11:30
  • Still wondering why was this question downvoted. – Ivan Cantarino Dec 06 '17 at 01:13

2 Answers2

10

Details

  • Xcode Version 10.3 (10G8), Swift 5

Solution

func areEqual (_ left: Any, _ right: Any) -> Bool {
    if  type(of: left) == type(of: right) &&
        String(describing: left) == String(describing: right) { return true }
    if let left = left as? [Any], let right = right as? [Any] { return left == right }
    if let left = left as? [AnyHashable: Any], let right = right as? [AnyHashable: Any] { return left == right }
    return false
}

extension Array where Element: Any {
    static func != (left: [Element], right: [Element]) -> Bool { return !(left == right) }
    static func == (left: [Element], right: [Element]) -> Bool {
        if left.count != right.count { return false }
        var right = right
        loop: for leftValue in left {
            for (rightIndex, rightValue) in right.enumerated() where areEqual(leftValue, rightValue) {
                right.remove(at: rightIndex)
                continue loop
            }
            return false
        }
        return true
    }
}
extension Dictionary where Value: Any {
    static func != (left: [Key : Value], right: [Key : Value]) -> Bool { return !(left == right) }
    static func == (left: [Key : Value], right: [Key : Value]) -> Bool {
        if left.count != right.count { return false }
        for element in left {
            guard   let rightValue = right[element.key],
                areEqual(rightValue, element.value) else { return false }
        }
        return true
    }
}

Usage

let comparisonResult = ["key1": 1, 2: "Value2"] == ["key1": ["key2":2]]     // false
print("!!!! \(comparisonResult)")

Some tests

func test(dict1: [AnyHashable : Any], dict2:  [AnyHashable : Any]) {
    print("========================")
    print("dict1: \(dict1)")
    print("dict2: \(dict2)")
    print("are\(dict1 == dict2 ? " " : " not ")equal")
}

test(dict1: ["key1": 1, 2: "Value2"],
     dict2: ["key1": 1, 2: "Value2"])

test(dict1: ["key1": 1, 2: "Value2"],
     dict2: ["key1": 1])

test(dict1: [2: "Value2"],
     dict2: ["key1": 1])

test(dict1: ["1": 1],
     dict2: [1: 1])

test(dict1: [1: 2],
     dict2: [1: 3])

test(dict1: ["key1": [1,2,3,4]],
     dict2: ["key1": [1,2,3,4]])

test(dict1: ["key1": [1,2,3,4]],
     dict2: ["key1": [2,1,3,4]])

test(dict1: ["key1": [1,2,3,4]],
     dict2: ["key1": [2,1,3]])

test(dict1: ["key1": [1,2,3,4]],
     dict2: ["key1": [1,2,3,"4"]])

test(dict1: ["key1": ["key2":2]],
     dict2: ["key1": ["key2":2]])

test(dict1: ["key1": ["key2":2]],
     dict2: ["key1": ["key2":3]])

test(dict1: ["key1": ["key2":2]],
     dict2: ["key1": ["key2":3]])

Tests results

========================
dict1: [AnyHashable("key1"): 1, AnyHashable(2): "Value2"]
dict2: [AnyHashable("key1"): 1, AnyHashable(2): "Value2"]
are equal
========================
dict1: [AnyHashable("key1"): 1, AnyHashable(2): "Value2"]
dict2: [AnyHashable("key1"): 1]
are not equal
========================
dict1: [AnyHashable(2): "Value2"]
dict2: [AnyHashable("key1"): 1]
are not equal
========================
dict1: [AnyHashable("1"): 1]
dict2: [AnyHashable(1): 1]
are not equal
========================
dict1: [AnyHashable(1): 2]
dict2: [AnyHashable(1): 3]
are not equal
========================
dict1: [AnyHashable("key1"): [1, 2, 3, 4]]
dict2: [AnyHashable("key1"): [1, 2, 3, 4]]
are equal
========================
dict1: [AnyHashable("key1"): [1, 2, 3, 4]]
dict2: [AnyHashable("key1"): [2, 1, 3, 4]]
are equal
========================
dict1: [AnyHashable("key1"): [1, 2, 3, 4]]
dict2: [AnyHashable("key1"): [2, 1, 3]]
are not equal
========================
dict1: [AnyHashable("key1"): [1, 2, 3, 4]]
dict2: [AnyHashable("key1"): [1, 2, 3, "4"]]
are not equal
========================
dict1: [AnyHashable("key1"): ["key2": 2]]
dict2: [AnyHashable("key1"): ["key2": 2]]
are equal
========================
dict1: [AnyHashable("key1"): ["key2": 2]]
dict2: [AnyHashable("key1"): ["key2": 3]]
are not equal
========================
dict1: [AnyHashable("key1"): ["key2": 2]]
dict2: [AnyHashable("key1"): ["key2": 3]]
are not equal
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • ' (Self.Type) -> (Self, Self) -> Bool' requires that 'Any' conform to 'Equatable' Type 'Any' does not conform to protocol 'Equatable' inside areEqual Function – Yaseen Majeed Apr 01 '20 at 16:28
  • @YaseenMajeed I can not understand your question. The code does not work? – Vasily Bodnarchuk Apr 01 '20 at 16:34
  • I was getting the above error message on this line of code inside the areEqual functions -----> if let left = left as? [AnyHashable: Any], let right = right as? [AnyHashable: Any] { return left == right } – Yaseen Majeed Apr 01 '20 at 17:36
  • @YaseenMajeed I checked the code. Everything is working. Can you share your usage example? – Vasily Bodnarchuk Apr 01 '20 at 17:48
2

Any does not conform to Equatable protocol. It is a must-have for a type if the == operator will be used. Therefore, you need to compare your Any objects using a function that takes a type parameter as mentioned in this answer:

func isEqual<T: Equatable>(type: T.Type, a: Any, b: Any) -> Bool? {
    guard let a = a as? T, let b = b as? T else { return nil }
    return a == b
}

However, to use this function, you should know the exact type of each value in typingAttributes. You can achieve this using the Mirror struct as follows:

let lilAny: Any = "What's my type? :("
print(Mirror(reflecting: lilAny).subjectType) // String
Dorukhan Arslan
  • 2,676
  • 2
  • 24
  • 42
  • 2
    @Dorukhan Arslan -- this doesn't appear to work; seems to be an issue of compile-time vs run-time. If you use code like: `let a: Any = 1 let b: Any = 1 let anyType = Mirror(reflecting: a).subjectType let result = isEqual(type: anyType, a: a, b: b)` the compiler cannot resolve the Mirror object and complains `error: cannot invoke 'isEqual' with an argument list of type '(type: Any.Type, a: Any, b: Any)'` – jstevenco Apr 12 '18 at 20:42