TruMan1 TruMan1 - 3 months ago 19
Swift Question

How to extract differences between dictionaries?

I can compare whether 2 dictionaries match or not like this:

func ==(lhs: [String: AnyObject], rhs: [String: AnyObject] ) -> Bool {
return NSDictionary(dictionary: lhs).isEqualToDictionary(rhs)
}


Is there a concise way to extract the differences between 2 dictionaries, as in the opposite of the intersection?

Answer

We can define an operator to check whether 2 dictionaries contains the same keys and for each key the same value.

Value must be Equatable

First of all we need to use generics to require that the Value of the dictionaries conforms to Equatable otherwise we won't be able to compare the values.

The code

Here's the code

func ==<Value:Equatable>(left: [String: Value], right: [String: Value]) -> Bool {
    guard left.keys.count == right.keys.count else { return  false }
    let differenceFound = zip(left.keys.sort(), right.keys.sort()).contains { elm -> Bool in
        let leftKey = elm.0
        let rightKey = elm.1
        return leftKey != rightKey || left[leftKey] != right[rightKey]
    }
    return !differenceFound
}

The first line verify that the dictionaries have the same number of entries, otherwise false is returned.

The next block of code sort the keys of both dictionaries and compare each pair looking for a pair where the keys or the values are different.

If such difference is not found then the dictionaries have the same keys and values, so they are equals.

Examples

["a":1, "b": 2] == ["a":1, "b": 2] // true

Of course since values inside a dictionaries don't have an order the following is still true

["a":1, "b": 2] == ["b":2, "a": 1] // true

In the next example we compare 2 dictionaries with a different number of values

["a":1, "b": 2] == ["a":1, "b": 2, "c": 3] // false

The != operator

Since we defined the == operator, Swift gifts us the != operator (which simply returns the opposite of ==), so we can also write

["a":1, "b": 2] != ["d":4] // true

Shorter version

The same operator can also be written in this shorter way

func ==<Value:Equatable>(left: [String: Value], right: [String: Value]) -> Bool {
    return
        left.keys.count == right.keys.count &&
        !zip(left.keys.sort(), right.keys.sort()).contains { $0 != $1 || left[$0] != right[$1] }
}

Update

Now given 2 dictionaries a and b, you want to perform a.minus(b) and get as result a new dictionaries containing all the (key, value) pairs available in a and not available in b.

Here's the code

extension Dictionary where Key: Comparable, Value: Equatable {
    func minus(dict: [Key:Value]) -> [Key:Value] {
        let entriesInSelfAndNotInDict = filter { dict[$0.0] != self[$0.0] }
        return entriesInSelfAndNotInDict.reduce([Key:Value]()) { (res, entry) -> [Key:Value] in
            var res = res
            res[entry.0] = entry.1
            return res
        }
    }
}

Examples

["a":1].minus(["a":2]) // ["a": 1]
["a":1].minus(["a":1]) // [:]
["a":1].minus(["a":1, "b":2]) // [:]
["a":1, "b": 2].minus(["a":1]) // ["b": 2]
Comments