84

Assuming dictionary keys and values have their equals and hash methods implemented correctly, what is the most succinct and efficient way to test for equality of two dictionaries?

In this context, two dictionaries are said to be equal if they contain the same set of keys (order not important), and for every such key, they agree on the value.

Here are some ways I came up with (there are probably many more):

public bool Compare1<TKey, TValue>(
    Dictionary<TKey, TValue> dic1, 
    Dictionary<TKey,TValue> dic2)
{
    return dic1.OrderBy(x => x.Key).
        SequenceEqual(dic2.OrderBy(x => x.Key));
}

public bool Compare2<TKey, TValue>(
    Dictionary<TKey, TValue> dic1, 
    Dictionary<TKey, TValue> dic2)
{
    return (dic1.Count == dic2.Count && 
        dic1.Intersect(dic2).Count().
        Equals(dic1.Count));
}

public bool Compare3<TKey, TValue>(
    Dictionary<TKey, TValue> dic1, 
    Dictionary<TKey, TValue> dic2)
{
    return (dic1.Intersect(dic2).Count().
        Equals(dic1.Union(dic2).Count()));
}
Pang
  • 9,564
  • 146
  • 81
  • 122
rony l
  • 5,798
  • 5
  • 35
  • 56

8 Answers8

138
dic1.Count == dic2.Count && !dic1.Except(dic2).Any();
Nick Jones
  • 6,413
  • 2
  • 18
  • 18
  • 3
    Sorry, have now added the missing not to make it correct. The dictionaries are equivalent if they are the same size and there are not any elements which are in the first and not in the second. – Nick Jones Sep 27 '10 at 14:57
  • 5
    Why is this correct? It does not respect the required equality of the values. It jut check the existence of all keys in both dictionaries. – Sebastian P.R. Gingter May 22 '13 at 09:13
  • 23
    @SebastianP.R.Gingter: A `Dictionary>` is also an instance of `IEnumerable>`. So you are comparing instances of `KeyValuePair`, which are equal if both the key and the value are equal. – Konamiman May 12 '14 at 09:00
  • 7
    Why is this accepted and upvoted? It doesn't do what the OP asked for, namely ***and for every such key, they agree on the value.*** – Mrchief Apr 16 '15 at 18:08
  • 11
    I believe this answer only works when the Dictionary's key and value types only use the built-in types or a custom class where the IEqualityComparer is setup correctly. Although, I would use `dict1.SequenceEqual(dict2)`. It will not work where the key or value is a collection, such as List. (See my answer.) – Machtyn Jun 29 '15 at 16:24
  • 8
    This answer is correct "**assuming [all] dictionary keys and values have their equals and hash methods implemented correctly**" - the method `except()` will perform a set difference on the `KeyValuePair`s in the dictionary, and each `KeyValuePair` will delegate to the `Equals` and `GetHashCode` methods on the keys and values (hence why these methods must be implemented correctly). If the keys and values are lists or dictionaries this won't work as expected, because these types just use reference equality for `Equals` and `GetHashCode`. – Singularity Apr 13 '17 at 00:26
  • 1
    I couldn't comment at the time I posted it, but I agree with Singularity and Nick Jones, assuming you've overwritten equals and gethashcode for all your objects, this method does work and is what I used. However, if you're going to use this as part of your object's equals method (assuming the object has a backing dictionary that's being compared), you should also implement an order agnostic GetHashCode() method. This is an example of one: https://stackoverflow.com/a/51953631/8333865 – Jason Masters Feb 04 '19 at 19:05
16

It really depends on what you mean by equality.

This method will test that two dictionaries contain the same keys with the same values (assuming that both dictionaries use the same IEqualityComparer<TKey> implementation).

public bool CompareX<TKey, TValue>(
    Dictionary<TKey, TValue> dict1, Dictionary<TKey, TValue> dict2)
{
    if (dict1 == dict2) return true;
    if ((dict1 == null) || (dict2 == null)) return false;
    if (dict1.Count != dict2.Count) return false;

    var valueComparer = EqualityComparer<TValue>.Default;

    foreach (var kvp in dict1)
    {
        TValue value2;
        if (!dict2.TryGetValue(kvp.Key, out value2)) return false;
        if (!valueComparer.Equals(kvp.Value, value2)) return false;
    }
    return true;
}
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • 2
    aren't you emptying the dictionary? comparex would fail the second time called since the second parameter is empty. why modify a dictionary - doesn't that violate a principle about a simple equality check? – NG. Sep 27 '10 at 14:20
  • @SB: Very good point. The `CompareX` method shouldn't really mutate either of the dictionaries passed to it. It was supposed to be a simple proof-of-concept, but I'll edit to make it safer. – LukeH Sep 27 '10 at 14:30
  • @LukeH: awesome - just wanted to make sure I was reading it properly. – NG. Sep 27 '10 at 14:31
  • @LukeH: This is a good answer, although most answers here appear to be `O(n log n)` with some fast-rejection going on. I would add a hash-check, which will make it will fast-reject in linear time with *high* probability. E.g. `int hash = dict.Aggregate(0, (hash, kvp) => hash ^ kvp.Key.GetHashCode() ^ kvp.Value.GetHashCode());` It adds nothing to the time-complexity. – Ani Sep 27 '10 at 16:53
  • 1
    @Ani: I don't really see how that would help. Generating and comparing the hashes will require a pass through both dictionaries, reading keys and values. If we generate and compare a hash of those keys and values then we get a "high probability" result; if we just compare them directly we get an exact answer. Am I overlooking something? – LukeH Sep 27 '10 at 17:06
  • When the dictionaries have equal counts, Case 1: They are unequal - the hash will almost certainly reject their equality in linear time. Case 2: They are equal - Your loop enters its worst case where it has to enumerate dict1 fully (`n`) and spend `nlogn` in getting the equivalent kvps out form dict2. In this case, the fast linear-time hash check hardly makes the run-time worse. – Ani Sep 27 '10 at 17:07
  • @Ani: Could you give some more details please? How would generating and comparing hashes (generated from keys/values) be any faster than just comparing the keys/values themselves? Pulling the info from `dict2` should be roughly O(1), assuming that the hash codes are half-decent. – LukeH Sep 27 '10 at 17:16
  • @LukeH: Ok, fair enough. I suppose it depends on whether you consider the retrieval to be `O(1)` (best case) or `O(log n)` (average case for most hashtables). IMO, the behaviour of most hashes coupled with the growth of the number of hash-buckets typically makes it `O(logn).` – Ani Sep 27 '10 at 17:18
  • doesn't the .Net equals semantic require that two null dictionaries be considered equal? – rony l Sep 28 '10 at 16:19
  • 1
    @rony: The first line of the method takes care of that. – LukeH Sep 28 '10 at 16:28
  • 1
    is this more efficient than Nick's answer? dic1.Count == dic2.Count && !dic1.Except(dic2).Any(); – rony l Sep 29 '10 at 09:18
  • 1
    @rony: The `Except` method works in a similar way to my answer. The performance should be very close, although I would expect mine to maybe have a *slight* edge: the `Except` method requires an initial pass through `dic2` to construct a separate set. You'd need to benchmark yourself to be sure, but I'd be surprised if there's any major difference. – LukeH Sep 29 '10 at 09:47
7

You could use linq for the key/value comparisons:

public bool Compare<TKey, TValue>(Dictionary<TKey, TValue> dict1, Dictionary<TKey, TValue dict2)
{
    IEqualityComparer<TValue> valueComparer = EqualityComparer<TValue>.Default;

    return  dict1.Count == dict2.Count &&
            dict1.Keys.All(key => dict2.ContainsKey(key) && valueComparer.Equals(dict1[key], dict2[key]));
}
Lee
  • 142,018
  • 20
  • 234
  • 287
  • 1
    What about `TValue val;` `return dict1.Count == dict2.Count && dict1.All(x => dict2.TryGetValue(x.Key, out val) && valueComparer.Equals(x.Value, val));` ? – Michael Sandler Jun 24 '15 at 12:59
3

Simple O(N) time, O(1) space solution with null checks

The other solutions using Set operations Intersect, Union or Except are good but these require additional O(N) memory for the final resultant dictionary which is just used for counting elements.

Instead, use Linq Enumerable.All to check this. First validate the count of two dictionaries, next, iterate over all D1's Key Value pairs and check if they are equal to D2's Key Value pairs. Note: Linq does allocate memory for a collection iterator but it's invariant of the collection size - O(1) space. Amortized complexity for TryGetValue is O(1).

// KV is KeyValue pair      
var areDictsEqual = d1.Count == d2.Count && d1.All(
     (d1KV) => d2.TryGetValue(d1KV.Key, out var d2Value) && (
          d1KV.Value == d2Value ||
          d1KV.Value?.Equals(d2Value) == true)
);
  • Why d1KV.Value == d2Value? - this is to check if object references are equal. Also, if both are null, d1KV.Value == d2Value will evaluate to true.

  • Why d1Kv.Value?.Equals(d2Value) == true? - Value?. is for null safe check and .Equals is meant to test equality of two objects based on your object's Equals and HashCode methods.

You can tweak the equality checks as you like. I'm assuming the Dict Values are nullable type to make the solution more generic (eg: string, int?, float?). If it's non-nullable type, the checks could be simplified.


Final note: In C# dictionary, the Keys can't be null. But Values can be null. Docs for reference.

Adithya Upadhya
  • 2,239
  • 20
  • 28
2

In addition to @Nick Jones answer, you're going to need to implement gethashcode in the same, order agnostic way. I would suggest something like this:

public override int GetHashCode()
{
        var hash = 13;
        var orderedKVPList = this.DictProp.OrderBy(kvp => kvp.Key);
        foreach (var kvp in orderedKVPList)
        {
                 hash = (hash * 7)  + kvp.Key.GetHashCode();
                 hash = (hash * 7)  + kvp.Value.GetHashCode();
        }
        return hash;
}
Steve
  • 25,806
  • 2
  • 33
  • 43
Jason Masters
  • 160
  • 2
  • 8
  • Hmmm I'm not so sure about this. Whenever you override the actual ``Equals`` method on an object, sure. But in that case, you'd want to make sure your type is immutable, otherwise it'll get lost if you put it into a collection and then mutate its state afterwards. So I don't think overriding ``Equals`` (and hashcode) is what we'd want here, because dictionaries are mutable. I think that's why you'll notice in other answers the careful usage of method names like "Compare" and "DictEquals" rather than "Equals" itself. – Colm Bhandal Jan 12 '22 at 13:19
1

I thought the accepted answer would be correct based on what I was reading in the smarthelp for the Except method: "Produces the set difference of two sequences by using the default equality comparer to compare values." But I discovered it is not a good answer.

Consider this code:

Dictionary<string, List<string>> oldDict = new Dictionary<string, List<string>>()
    {{"001A", new List<string> {"John", "Doe"}},
     {"002B", new List<string> {"Frank", "Abignale"}},
     {"003C", new List<string> {"Doe", "Jane"}}};
Dictionary<string, List<string>> newDict = new Dictionary<string, List<string>>()
    {{"001A", new List<string> {"John", "Doe"}},
     {"002B", new List<string> {"Frank", "Abignale"}},
     {"003C", new List<string> {"Doe", "Jane"}}};

bool equal = oldDict.Count.Equals(newDict.Count) && !oldDict.Except(newDict).Any();
Console.WriteLine(string.Format("oldDict {0} newDict", equal?"equals":"does not equal"));
equal = oldDict.SequenceEqual(newDict);
Console.WriteLine(string.Format("oldDict {0} newDict", equal ? "equals" : "does not equal"));

Console.WriteLine(string.Format("[{0}]", string.Join(", ", 
    oldDict.Except(newDict).Select(k => 
        string.Format("{0}=[{1}]", k.Key, string.Join(", ", k.Value))))));

This results in the following:

oldDict does not equal newDict
oldDict does not equal newDict
[001A=[John, Doe], 002B=[Frank, Abignale], 003C=[Doe, Jane]]

As you can see, both "oldDict" and "newDict" are setup exactly the same. And neither the suggested solution nor a call to SequenceEqual works properly. I wonder if it is a result of the Except using lazy loading or the way the comparer is setup for the Dictionary. (Although, looking at the structure and reference explanations suggest it should.)

Here's the solution I came up with. Note that the rule I used is as follows: two dictionaries are equal if both contain the same keys and the values for each key match. Both keys and values must be in the same sequential order. And my solution may not be the most efficient, since it relies on iterating through the entire set of keys.

private static bool DictionaryEqual(
    Dictionary<string, List<string>> oldDict, 
    Dictionary<string, List<string>> newDict)
{
    // Simple check, are the counts the same?
    if (!oldDict.Count.Equals(newDict.Count)) return false;

    // Verify the keys
    if (!oldDict.Keys.SequenceEqual(newDict.Keys)) return false;

    // Verify the values for each key
    foreach (string key in oldDict.Keys)
        if (!oldDict[key].SequenceEqual(newDict[key]))
            return false;

    return true;
}

Also see how the results change if: Key order is not the same. (returns false)

newDict = new Dictionary<string, List<string>>()
    {{"001A", new List<string> {"John", "Doe"}},
     {"003C", new List<string> {"Doe", "Jane"}},
     {"002B", new List<string> {"Frank", "Abignale"}}};

and Key order matches, but Value does not match (returns false)

newDict = new Dictionary<string, List<string>>()
    {{"001A", new List<string> {"John", "Doe"}},
     {"002B", new List<string> {"Frank", "Abignale"}},
     {"003C", new List<string> {"Jane", "Doe"}}};

If sequence order does not matter, the function can be changed to the following, but there is likely a performance hit.

private static bool DictionaryEqual_NoSort(
    Dictionary<string, List<string>> oldDict,
    Dictionary<string, List<string>> newDict)
{
    // Simple check, are the counts the same?
    if (!oldDict.Count.Equals(newDict.Count)) return false;

    // iterate through all the keys in oldDict and
    // verify whether the key exists in the newDict
    foreach(string key in oldDict.Keys)
    {
        if (newDict.Keys.Contains(key))
        {
            // iterate through each value for the current key in oldDict and 
            // verify whether or not it exists for the current key in the newDict
            foreach(string value in oldDict[key])
                if (!newDict[key].Contains(value)) return false;
        }
        else { return false; }
    }

    return true;
}

Check out if the DictionaryEqual_NoSort using the following for newDict (DictionaryEquals_NoSort returns true):

newDict = new Dictionary<string, List<string>>()
    {{"001A", new List<string> {"John", "Doe"}},
     {"003C", new List<string> {"Jane", "Doe"}},
     {"002B", new List<string> {"Frank", "Abignale"}}};     
Machtyn
  • 2,982
  • 6
  • 38
  • 64
  • In my DictionaryEquals method, I was unsure whether or not I need the Count check. Does SequenceEqual do that already? – Machtyn Jun 29 '15 at 16:14
  • Also, if my setup of the accepted answer and proof that it fails is incorrect, please feel free to correct me. – Machtyn Jun 29 '15 at 16:15
  • I am surprised that `List` isn't returning `Equals` correctly. I could see it failing for a custom class that didn't override `Equals` but I am surprised to see this behavior with a list. – James McMahon Mar 25 '16 at 16:31
  • @Machtyn List does not override Equals and Hashcode. Thus, the `Except` call in your original example get Equals false for the Lists even though they contain the "same" elements - they are being compared using reference equality, which is obviously false. – Alexander Høst Mar 01 '20 at 12:46
1

@Allen's answer:

bool equals = a.Intersect(b).Count() == a.Union(b).Count()

is about arrays but as far as IEnumerable<T> methods are used, it can be used for Dictionary<K,V> too.

Community
  • 1
  • 1
abatishchev
  • 98,240
  • 88
  • 296
  • 433
-1

If two dictionaries contain the same keys, but in different order, should they be considered equal? If not, then the dictionaries should be compared by running enumerators through both simultaneously. This will probably be faster than enumerating through one dictionary and looking up each element in the other. If you have advance knowledge that equal dictionaries will have their elements in the same order, such a double-enumeration is probably the way to go.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Depends on your application, I suppose. In my particular case, Key order doesn't matter and order of the values, when compared with like Key, doesn't matter. – Machtyn Jun 29 '15 at 14:53
  • If you need an order-independent comparison, a custom dictionary type which included engineered-in support for such a thing could probably be faster than any of the built-in types. Otherwise if you control when items are added to or removed from dictionaries, it may be helpful to compute the hash code of each item that's added or removed and keep a running `UInt64` total of `(hash+0x123456789L)*hash`, doing the computation in an `unchecked` context [when items are added, add the above value to the total; when removed, subtract it]. If two collections have unequal totals... – supercat Jun 29 '15 at 15:04
  • ...there's no need to compare their contents. Likewise if they have unequal sizes. If the sizes are equal, and the summed extended hashes are equal, and one can presume that the collections use the same `EqualityComparer`, iterate through one and check to see if the other contains all the items. – supercat Jun 29 '15 at 15:05