Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing two [String: Any] dictionaries in Swift 4

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

like image 504
Ivan Cantarino Avatar asked Dec 04 '17 11:12

Ivan Cantarino


People also ask

How do I compare two dictionaries in Swift?

Without custom type in value of Dictionary, in Swift 2+ you can use the == operator to compare two Dictionary to check if they are equal or not. But in some cases with custom types as the Dictionary 's value (like struct ), you must adopt Equatable in order for that custom type to use == operator.

Are dictionaries passed by reference Swift?

Instances of your SharedDictionary will be passed-by-reference (not copied).

How dictionary works internally Swift?

dictionary in Swift is used to store elements. Dictionary also contains one key while storing elements to it, later on, we can use these keys to access the elements store in the dictionary. Dictionary in swift does not maintain the insertion order of the element in which they are stored, they are unordered in nature.


2 Answers

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
like image 180
Vasily Bodnarchuk Avatar answered Oct 17 '22 11:10

Vasily Bodnarchuk


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
like image 29
Dorukhan Arslan Avatar answered Oct 17 '22 12:10

Dorukhan Arslan