Comparing two dictionaries for equal data in c#?
Asked Answered
B

5

22

I have two dictionaries containing a string key and then an object. The object contains five fields. Is there an elegant way to ensure both dictionaries first contain the same keys and then if this is correct, contain the same five fields per object?

Would the two dictionaries have the same in-built hashcode or something?

EDIT, doesn't appear to be working for the following code:

Dictionary<string, MyClass> test1 = new Dictionary<string, MyClass>();
Dictionary<string, MyClass> test2 = new Dictionary<string, MyClass>();

MyClass i = new MyClass("", "", 1, 1, 1, 1);
MyClass j = new MyClass("", "", 1, 1, 1, 1);

test1.Add("1", i);
test2.Add("1", j);

bool equal = test1.OrderBy(r => r.Key).SequenceEqual(test2.OrderBy(r => r.Key));

class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a= aa;
        b= bb;
        c= cc;
        d= dd;
        e= ee;
        f= ff;
    }

this returns false?

Boykin answered 20/11, 2012 at 9:40 Comment(0)
M
22

First you have to override Equals and GetHashCode method in your class, otherwise comparison will be performed on references instead of actual values. (The code to override Equals and GetHashCode is provided at the end), after that you can use:

var result = (dic1 == dic2) || //Reference comparison (if both points to same object)
             (dic1.Count == dic2.Count && !dic1.Except(dic2).Any());

Since the order in which items in Dictionary are returned is undefined, you can not rely on Dictionary.SequenceEqual (without OrderBy).

You can try:

Dictionary<string, object> dic1 = new Dictionary<string, object>();
Dictionary<string, object> dic2 = new Dictionary<string, object>();
dic1.Add("Key1", new { Name = "abc", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });

dic2.Add("Key1",new { Name = "abc",Number=  "123", Address= "def", Loc="xyz"});
dic2.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });


bool result = dic1.SequenceEqual(dic2); //Do not use that

Most of the time the above will return true, but one can't really rely on that due to unordered nature of Dictionary.

Since SequenceEqual will compare the order as well, therefore relying on only SequenceEqual could be wrong. You have to use OrderBy to order both dictionaries and then use SequenceEqual like:

bool result2 = dic1.OrderBy(r=>r.Key).SequenceEqual(dic2.OrderBy(r=>r.Key));

But that will involve multiple iterations, once for ordering and the other for comparing each element using SequenceEqual.

Code for overriding Equals and GetHashCode

private class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a = aa;
        b = bb;
        c = cc;
        d = dd;
        e = ee;
        f = ff;
    }

    protected bool Equals(MyClass other)
    {
        return string.Equals(a, other.a) && string.Equals(b, other.b) && c == other.c && e == other.e && d == other.d && f == other.f;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyClass)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = (a != null ? a.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ (b != null ? b.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ c.GetHashCode();
            hashCode = (hashCode * 397) ^ e.GetHashCode();
            hashCode = (hashCode * 397) ^ d.GetHashCode();
            hashCode = (hashCode * 397) ^ f.GetHashCode();
            return hashCode;
        }
    }
}

You may also see: Correct way to override Equals() and GetHashCode()

Microwave answered 20/11, 2012 at 9:45 Comment(12)
This relies on the items being in order too, which may work but doesn't seem wise.Thorvald
You could sort both of them first, OPs question doesn't stipulate that they have to be in the same order.Gravestone
@PhonicUK, I was just testing that and it seems to work even if the order is different. Just updated my answer accordingly.Microwave
@All of you. I'm not worried about ordering, therefore i guess I will have to order both dictionaries before checking?Boykin
@mezamorphic, yup, then you are good to go, Also the line for result2 is ordering the dictionaries for just check the sequence equal, it is not modifying the original dictionary objectsMicrowave
@Microwave One of my fields is a decimal and in one of the dictionaries the value is 1 and in the other its 1.0000 i have a feeling it is flagging this as a difference, could you confirm and is there a solution? I have 1.0000 because the data is declared as decimal in SQL Server.Boykin
@mezamorphic, Nope it shouldn't. Just tested it with the above data with Number changed to decimal type having values of Number = 1M and Number = 1.00M. Result is trueMicrowave
@Microwave Doesnt seem to be working for the code I am writing, have edited question with code :sBoykin
@mezamorphic, also add the basic class structure with the constructor you are usingMicrowave
let us continue this discussion in chatBoykin
I'm not sure that using SequenceEqual is the best idea here. Dictionaries aren't really meant to be ordered collections; they're hash tables. SequenceEqual with dictionaries is going to require us to do unnecessary iterations: one for each dictionary to create temporary ordered collections and then iterations for each of the ordered collections to compare them for equality. Sure, it's easy to chain a couple of LINQ methods, but you might want to make a mention of the trade off.Rodin
@greyseal96, thanks for pointing it out, I get a chance to revise my answer.Microwave
T
30

You can use

bool dictionariesEqual = 
    dic1.Keys.Count == dic2.Keys.Count &&
    dic1.Keys.All(k => dic2.ContainsKey(k) && object.Equals(dic2[k], dic1[k]));
Thorvald answered 20/11, 2012 at 9:50 Comment(9)
dic2[k] == dic1[k] if the value is reference type, then it will only compare the reference , not the exact values. (fields)Microwave
+1 as this avoids sorting, which would potentially make it faster on large sets.Terrorstricken
@Habib, in the example the value is an anonymous type to equality should work, but needs changing to dic2[k].Equals(dic1[k])Terrorstricken
@Microwave Also doesn't the same apply to comparing KeyValuePair<object>s? (Genuine question, not sure whether it does or not.) (Edit: Just tried, apparently not!)Thorvald
@Rawling, Try it in Visual studio with example data, it seems to be working with sequenceequal, but your query is returning false.Microwave
@Rawling, I think maybe it should be dic2[k].Equals(dic1[k]), see here: #12124012Terrorstricken
@JustinHarvey I've used object.Equals in case one or both of the items are null, which seems to work.Thorvald
Does not work if the Dictionary value is a Collection such as List<T>. Use dic1[k].SequenceEqual(dic2[k]) if this is the case.Curtilage
object.Equals will box value types. Better to use EqualityComparer<T>.Default.Equals().Illmannered
M
22

First you have to override Equals and GetHashCode method in your class, otherwise comparison will be performed on references instead of actual values. (The code to override Equals and GetHashCode is provided at the end), after that you can use:

var result = (dic1 == dic2) || //Reference comparison (if both points to same object)
             (dic1.Count == dic2.Count && !dic1.Except(dic2).Any());

Since the order in which items in Dictionary are returned is undefined, you can not rely on Dictionary.SequenceEqual (without OrderBy).

You can try:

Dictionary<string, object> dic1 = new Dictionary<string, object>();
Dictionary<string, object> dic2 = new Dictionary<string, object>();
dic1.Add("Key1", new { Name = "abc", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });

dic2.Add("Key1",new { Name = "abc",Number=  "123", Address= "def", Loc="xyz"});
dic2.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });


bool result = dic1.SequenceEqual(dic2); //Do not use that

Most of the time the above will return true, but one can't really rely on that due to unordered nature of Dictionary.

Since SequenceEqual will compare the order as well, therefore relying on only SequenceEqual could be wrong. You have to use OrderBy to order both dictionaries and then use SequenceEqual like:

bool result2 = dic1.OrderBy(r=>r.Key).SequenceEqual(dic2.OrderBy(r=>r.Key));

But that will involve multiple iterations, once for ordering and the other for comparing each element using SequenceEqual.

Code for overriding Equals and GetHashCode

private class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a = aa;
        b = bb;
        c = cc;
        d = dd;
        e = ee;
        f = ff;
    }

    protected bool Equals(MyClass other)
    {
        return string.Equals(a, other.a) && string.Equals(b, other.b) && c == other.c && e == other.e && d == other.d && f == other.f;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyClass)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = (a != null ? a.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ (b != null ? b.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ c.GetHashCode();
            hashCode = (hashCode * 397) ^ e.GetHashCode();
            hashCode = (hashCode * 397) ^ d.GetHashCode();
            hashCode = (hashCode * 397) ^ f.GetHashCode();
            return hashCode;
        }
    }
}

You may also see: Correct way to override Equals() and GetHashCode()

Microwave answered 20/11, 2012 at 9:45 Comment(12)
This relies on the items being in order too, which may work but doesn't seem wise.Thorvald
You could sort both of them first, OPs question doesn't stipulate that they have to be in the same order.Gravestone
@PhonicUK, I was just testing that and it seems to work even if the order is different. Just updated my answer accordingly.Microwave
@All of you. I'm not worried about ordering, therefore i guess I will have to order both dictionaries before checking?Boykin
@mezamorphic, yup, then you are good to go, Also the line for result2 is ordering the dictionaries for just check the sequence equal, it is not modifying the original dictionary objectsMicrowave
@Microwave One of my fields is a decimal and in one of the dictionaries the value is 1 and in the other its 1.0000 i have a feeling it is flagging this as a difference, could you confirm and is there a solution? I have 1.0000 because the data is declared as decimal in SQL Server.Boykin
@mezamorphic, Nope it shouldn't. Just tested it with the above data with Number changed to decimal type having values of Number = 1M and Number = 1.00M. Result is trueMicrowave
@Microwave Doesnt seem to be working for the code I am writing, have edited question with code :sBoykin
@mezamorphic, also add the basic class structure with the constructor you are usingMicrowave
let us continue this discussion in chatBoykin
I'm not sure that using SequenceEqual is the best idea here. Dictionaries aren't really meant to be ordered collections; they're hash tables. SequenceEqual with dictionaries is going to require us to do unnecessary iterations: one for each dictionary to create temporary ordered collections and then iterations for each of the ordered collections to compare them for equality. Sure, it's easy to chain a couple of LINQ methods, but you might want to make a mention of the trade off.Rodin
@greyseal96, thanks for pointing it out, I get a chance to revise my answer.Microwave
I
1

The built-in Equals function of Dictionary<T> only checks for reference equality, see this question on SO. Hashcodes do not reliably tell you if two objects are equal; there is always a chance of hash collision. Never use hashcodes as an equality test!

I would do it by hand: Compare the entry count of both dictionaries, iterate over the key-value-pairs of one dictionary and check if the key exists in the other one and compare the corresponding objects from both dictionaries. Edit: See Rawling's answer :)

Innominate answered 20/11, 2012 at 9:54 Comment(1)
I think that this answer is headed towards the best solution. Dictionaries are not intended to be ordered collections so using SequenceEquals(), which is an extension method for Enumerable and requires the two sequences to be ordered, is forcing an inefficiency on the dictionaries. Rawling's answer is pretty good, but I don't think it's a good idea to use object.Equals to compare the dictionary values.Rodin
R
1

There were a couple of answers on here that I think got pretty close but there were a couple of additional points that I thought should be added so I'm adding them as another possible answer.

First, I would avoid using the SequenceEquals method. It is an extension method for Enumerable and implicitly requires the two collections to be in the same order. Dictionaries are not meant to be ordered collections so using SequenceEquals means that you'll have to needlessly iterate over both of the dictionaries to create sorted/ordered intermediary collections that you also don't need and then iterate over those collections to compare them for equality. That seems really inefficient and an abuse of LINQ, all in the name of trying to be terse and write a one line solution. If the OP's idea of "elegant" is terse, I guess this will do the trick, but it seems wasteful.

On the other hand, if the OP's idea of "elegant" is efficient, then you'll probably need to write a little more code. First, you should either override the Equals method for your class or implement IEquatable in your class (see here, for example). This will allow you to compare the values in the dictionary. Then, you'll probably want to do something like implement an interface like IEqualityComparer for your dictionary.

Then, the comparison of the two dictionaries would go something like below. It's just a quick and dirty "back of the napkin" example so it's not an example of the best way to do it, but it's meant to illustrate a way to iterate only as many times over the dictionary as necessary and quit out as soon as an inequality is found.

First the required code:

public class Foo
{
    //members here...
    public override bool Equals(object obj)
    {
        //implementation here
    }
    //You should probably also override GetHashCode to be thorough,
    //but that's an implementation detail...
}

//This method could stand on its own or you could change it to make it 
//part of the implementation of one of the comparison interfaces...
bool DictionariesEqual(Dictionary<K, V> x, Dictionary<K, V> y)
{
    //If we're comparing the same object, it's obviously equal to itself.
    if(x == y)
    {
        return true;
    }
    //Make sure that we don't have null objects because those are
    //definitely not equal.
    if (x == null || y == null)
    {
        return false;
    }
    
    //Stop processing if at any point the dictionaries aren't equal.
    bool result = false;

    //Make sure that the dictionaries have the same count.
    result = x.Count == y.Count;
    
    //If we passed that check, keep going.
    if(result)
    {
        foreach(KeyValuePair<K, V> xKvp in x)
        {
            //If we don't have a key from one in the other, even though
            //the counts are the same, the dictionaries aren't equal so
            //we can fail out.
            V yValue;
            if(!y.TryGetValue(xKvp.Key, out yValue))
            {
                result = false;
                break;
            }
            else
            {
                //Use the override of the Equals method for your object
                //to see if the value from y is equal to the value from
                //x.
                result = xKvp.Value.Equals(yValue);
                if(!result)
                {
                    //If they're not equal we can just quit out.
                    break;
                }
            }
        }
    }
    return result;
}

Then we'd use it like this:

Dictionary<string, Foo> dict1 = new Dictionary<string, Foo>();
Dictionary<string, Foo> dict2 = new Dictionary<string, Foo>();
//Fill the dictionaries here...

//Compare the dictionaries
bool areDictsEqual = DictionariesEqual(dict1, dict2);

So, it's not the most terse code, but it's also not iterating more than necessary. In my opinion, that's more elegant.

Edit: A commenter suggested that the code examples get updated to make them generic. I originally just went off of the example of the original poster but making this generic seemed like a good idea with one caveat: Be careful what the type of the key is. If it's a simple type like a string or an int that C# includes equality implementations for, everything should be good. If the key is a custom type, you'll need to make sure that you override Equals() and GetHashCode() for your custom type.

Rodin answered 25/8, 2015 at 21:11 Comment(0)
L
0

In this case you can just use the SequenceEquals()-Method, like following:

   Dictionary<string, object> d1 = new Dictionary<string, object>();
   d1.Add("first", new { Name = "TestName", Age = 12, ID = 001 }); 

   Dictionary<string, object> d2 = new Dictionary<string, object>();
   d2.Add("first", new { Name = "TestName", Age = 12, ID = 001 });

   Console.WriteLine(d1.SequenceEqual(d2)); //outputs True                

Note: For simplicity i used implicit classes to fill the Dictionaries. The code will work the same way with any objects. The hashcodes of both dictionaries are not equal, which can be easily verified by doing the following:

   Console.WriteLine(d1.GetHashCode() + " " + d2.GetHashCode()); //outputs different hashcodes
Levan answered 20/11, 2012 at 9:53 Comment(1)
This is not reliable, dictionaries don't always output in the same order. Order can change depending on the order items are added or the capacity of the dictionary.Illmannered

© 2022 - 2024 — McMap. All rights reserved.