Thabo
Thabo

Reputation: 1554

How to compare two Dictionaries in C#

I have two generic Dictionaries. Both have the same keys, but their values can be different. I want to compare the 2nd dictionary with the 1st dictionary. If there are differences between their values, I want to store those values in a separate dictionary.

1st Dictionary
------------
key       Value

Barcode   1234566666
Price     20.00


2nd Dictionary
--------------
key       Value

Barcode   1234566666
Price     40.00


3rd Dictionary
--------------
key       Value

Price     40

Can anyone give me the best algorithm to do this? I wrote an algorithm but it has a lot of loops. I am seeking a short and efficient idea, like a solution using LINQ query expressions or LINQ lambda expressions. I am using .NET Framework 3.5 with C#. I found something about the Except() method, but unfortunately I couldn't understand what is happening on that method. It would be great if anyone could explain the suggested algorithm.

Upvotes: 56

Views: 125838

Answers (14)

schoetbi
schoetbi

Reputation: 12856

I use this extension method to compare two dictionaries

public static class Extensions
{
    public static bool IsEquivalent<k, v>(this IDictionary<k, v> a, IDictionary<k, v> b)
    {
        return a.Keys.ToHashSet().SetEquals(b.Keys.ToHashSet()) &&
               a.All(p => Equals(p.Value, b[p.Key]));
    }
}

Upvotes: 0

Freek Wiekmeijer
Freek Wiekmeijer

Reputation: 4940

A simple LINQ function that compares the keys and values

using System.Collections.Generic;
using System.Linq;

bool DictionariesAreEqual(Dictionary<object, object> d1, Dictionary<object, object> d2)
{
    // If any keys are missing/extra/different, the dicts are not the same
    if (!d1.Keys.ToHashSet().SetEquals(d2.Keys.ToHashSet()))
        return false;

    // Next we count the differences between the corresponding values d1[key] and d2[key]
     return d1.Where((kvp) => kvp.Value != d2[kvp.Key]).Count() == 0;
}

Upvotes: 0

Sajjad.HS
Sajjad.HS

Reputation: 393

I would compare two dictionaries in this way:

Dictionary<string, string> dict1 = new Dictionary<string, string>()
{
    {"1","1" },
    {"2","1" },
    {"3","3" },
};

Dictionary<string, string> dict2 = new Dictionary<string, string>()
{
    {"1","1" },
    {"3","3" },
    {"2","1" },
};

var areEqaul = dict1.Count == dict2.Count && !dict1.Keys.Any(key => !dict2.Keys.Contains(key)) &&
    !dict1.Keys.Any(key => dict2[key] != dict1[key]));

Basically, I check if the keys are whether same or not if keys were the same then check the corresponding values per key in both dictionaries

Upvotes: 1

jlo-gmail
jlo-gmail

Reputation: 5038

Serialize the Dictionaries to a string:

var jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new OrderedContractResolver() };
var sourceHash = JsonConvert.SerializeObject(dictionary1, jsonSerializerSettings).GetHashCode();
var blobHash = JsonConvert.SerializeObject(dictionary2, jsonSerializerSettings).GetHashCode();

public class OrderedContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList();
    }
}

Then compare the HashCodes (or just the json strings). This code forces the parameters to appear in consistent order, so if your dictionaries contain the same values out of order, they should return equal.

This solution avoids the edge case of comparing a dictionary which is a subset to a larger dictionary, which will return equal in many of the other answers. This solution will likely be inefficient for very large dictionaries or if you are making a large number of comparisons - but it is simple.

Upvotes: -1

J Scott
J Scott

Reputation: 1019

if you need a full comparison of both (think venn diagram results), there are four discrete outcomes for a given key assuming it is present in at least one of the dictionaries

public enum KeyCompareResult
{
  ValueEqual,
  NotInLeft,
  NotInRight,
  ValueNotEqual,
}

To get all of the keys in a dictionary, use dictionary.Keys. To get the set of keys in either dictionary, use Enumerable.Union, which will combine the sets and filter out duplicates.

Assuming you want some more generic method, you can then write your comparison as

public IEnumerable<KeyValuePair<TKey, KeyCompareResult>> GetDifferences<TKey, TValue>(
    IDictionary<TKey, TValue> leftDict,
    IDictionary<TKey, TValue> rightDict,
    IEqualityComparer<TValue> comparer = null)
{
    var keys = leftDict.Keys.Union(rightDict.Keys);
    comparer ??= EqualityComparer<TValue>.Default;
    return keys.Select(key =>
    {
        if (!leftDict.TryGetValue(key, out var left))
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.NotInLeft);
        }
        else if (!rightDict.TryGetValue(key, out var right))
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.NotInRight);
        }
        else if (!comparer.Equals(left, right))
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.ValueNotEqual);
        }
        else
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.ValueEqual);
        }
    });
}

The left/right distinction isn't obvious unless you are looking at the method call, so, you probably want to select the method results into messages or some other more meaningful data structure

var left = new Dictionary<int, string> { { 1, "one" }, { 2, "two" }, { 4, "four" } };
var right = new Dictionary<int, string> { { 1, "one" }, { 2, "A different value" }, { 3, "three" } };

GetDifferences(left, right, StringComparer.InvariantCulture)
    .Display(); // or however you want to process the data
/*
Key Value
--- -------------
  1 ValueEqual 
  2 ValueNotEqual 
  4 NotInRight 
  3 NotInLeft 
*/

Upvotes: 1

TheConstructor
TheConstructor

Reputation: 4465

In recent C# versions you can try

        public static Dictionary<TK, TV> ValueDiff<TK, TV>(this Dictionary<TK, TV> dictionary,
            Dictionary<TK, TV> otherDictionary)
        {
            IEnumerable<(TK key, TV otherValue)> DiffKey(KeyValuePair<TK, TV> kv)
            {
                var otherValue = otherDictionary[kv.Key];
                if (!Equals(kv.Value, otherValue))
                {
                    yield return (kv.Key, otherValue);
                }
            }

            return dictionary.SelectMany(DiffKey)
                .ToDictionary(t => t.key, t => t.otherValue, dictionary.Comparer);
        }

I am not sure that SelectManyis always the fastest solution, but it is one way to only select the relevant items and generate the resulting entries in the same step. Sadly C# does not support yield return in lambdas and while I could have constructed single or no item collections, I choose to use an inner function.

Oh and as you say that the keys are the same, it may be possible to order them. Then you could use Zip

        public static Dictionary<TK, TV> ValueDiff<TK, TV>(this Dictionary<TK, TV> dictionary,
            Dictionary<TK, TV> otherDictionary)
        {
            return dictionary.OrderBy(kv => kv.Key)
                .Zip(otherDictionary.OrderBy(kv => kv.Key))
                .Where(p => !Equals(p.First.Value, p.Second.Value))
                .ToDictionary(p => p.Second.Key, p => p.Second.Value, dictionary.Comparer);
        }

Personally I would tend not to use Linq, but a simple foreach like carlosfigueira and vanfosson:

        public static Dictionary<TK, TV> ValueDiff2<TK, TV>(this Dictionary<TK, TV> dictionary,
            Dictionary<TK, TV> otherDictionary)
        {
            var result = new Dictionary<TK, TV>(dictionary.Count, dictionary.Comparer);
            foreach (var (key, value) in dictionary)
            {
                var otherValue = otherDictionary[key];
                if (!Equals(value, otherValue))
                {
                    result.Add(key, otherValue);
                }
            }

            return result;
        }

Upvotes: 1

Mohamed.Abdo
Mohamed.Abdo

Reputation: 2200

converting the object to dictionary then following set concept subtract them, result items should be empty in case they are identically.

 public static IDictionary<string, object> ToDictionary(this object source)
    {
        var fields = source.GetType().GetFields(
            BindingFlags.GetField |
            BindingFlags.Public |
            BindingFlags.Instance).ToDictionary
        (
            propInfo => propInfo.Name,
            propInfo => propInfo.GetValue(source) ?? string.Empty
        );

        var properties = source.GetType().GetProperties(
            BindingFlags.GetField |
            BindingFlags.GetProperty |
            BindingFlags.Public |
            BindingFlags.Instance).ToDictionary
        (
            propInfo => propInfo.Name,
            propInfo => propInfo.GetValue(source, null) ?? string.Empty
        );

        return fields.Concat(properties).ToDictionary(key => key.Key, value => value.Value); ;
    }
    public static bool EqualsByValue(this object source, object destination)
    {
        var firstDic = source.ToFlattenDictionary();
        var secondDic = destination.ToFlattenDictionary();
        if (firstDic.Count != secondDic.Count)
            return false;
        if (firstDic.Keys.Except(secondDic.Keys).Any())
            return false;
        if (secondDic.Keys.Except(firstDic.Keys).Any())
            return false;
        return firstDic.All(pair =>
          pair.Value.ToString().Equals(secondDic[pair.Key].ToString())
        );
    }
    public static bool IsAnonymousType(this object instance)
    {

        if (instance == null)
            return false;

        return instance.GetType().Namespace == null;
    }
    public static IDictionary<string, object> ToFlattenDictionary(this object source, string parentPropertyKey = null, IDictionary<string, object> parentPropertyValue = null)
    {
        var propsDic = parentPropertyValue ?? new Dictionary<string, object>();
        foreach (var item in source.ToDictionary())
        {
            var key = string.IsNullOrEmpty(parentPropertyKey) ? item.Key : $"{parentPropertyKey}.{item.Key}";
            if (item.Value.IsAnonymousType())
                return item.Value.ToFlattenDictionary(key, propsDic);
            else
                propsDic.Add(key, item.Value);
        }
        return propsDic;
    }
originalObj.EqualsByValue(messageBody); // will compare values.

source of the code

Upvotes: 0

Royi Namir
Royi Namir

Reputation: 148524

try :

dictionary1.OrderBy(kvp => kvp.Key)
           .SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key))

Upvotes: 58

carlosfigueira
carlosfigueira

Reputation: 87228

You mentioned that both dictionaries have the same keys, so if this assumption is correct, you don't need anything fancy:

        foreach (var key in d1.Keys)
        {
            if (!d1[key].Equals(d2[key]))
            {
                d3.Add(key, d2[key]);
            }
        }

Or am I misunderstanding your problem?

Upvotes: 15

Saeed Amiri
Saeed Amiri

Reputation: 22555

var diff1 = d1.Except(d2);
var diff2 = d2.Except(d1);
return diff1.Concat(diff2);

Edit: If you sure all keys are same you can do:

var diff = d2.Where(x=>x.Value != d1[x.Key]).ToDictionary(x=>x.Key, x=>x.Value);

Upvotes: 4

Adi Lester
Adi Lester

Reputation: 25201

Assuming both dictionaries have the same keys, the simplest way is

var result = a.Except(b).ToDictionary(x => x.Key, x => x.Value);

EDIT

Note that a.Except(b) gives a different result from b.Except(a):

a.Except(b): Price     20
b.Except(a): Price     40

Upvotes: 5

Jon Skeet
Jon Skeet

Reputation: 1500535

If you've already checked that the keys are the same, you can just use:

var dict3 = dict2.Where(entry => dict1[entry.Key] != entry.Value)
                 .ToDictionary(entry => entry.Key, entry => entry.Value);

To explain, this will:

  • Iterate over the key/value pairs in dict2
  • For each entry, look up the value in dict1 and filter out any entries where the two values are the same
  • Form a dictionary from the remaining entries (i.e. the ones where the dict1 value is different) by taking the key and value from each pair just as they appear in dict2.

Note that this avoids relying on the equality of KeyValuePair<TKey, TValue> - it might be okay to rely on that, but personally I find this clearer. (It will also work when you're using a custom equality comparer for the dictionary keys - although you'd need to pass that to ToDictionary, too.)

Upvotes: 59

Chamika Sandamal
Chamika Sandamal

Reputation: 24312

to check any difference,

dic1.Count == dic2.Count && !dic1.Except(dic2).Any();

following code return all the different values

dic1.Except(dic2) 

Upvotes: 47

tvanfosson
tvanfosson

Reputation: 532465

You should be able to join them on their keys and select both values. Then you can filter based on whether the values are the same or different. Finally, you can convert the collection to a dictionary with the keys and second values.

  var compared = first.Join( second, f => f.Key, s => s.Key, (f,s) => new { f.Key, FirstValue = f.Value, SecondValue = s.Value } )
                      .Where( j => j.FirstValue != j.SecondValue )
                      .ToDictionary( j => j.Key, j => j.SecondValue );

Using a loop shouldn't be too bad either. I suspect that they would have similar performance characteristics.

  var compared = new Dictionary<string,object>();
  foreach (var kv in first)
  {
      object secondValue;
      if (second.TryGetValue( kv.Key, out secondValue ))
      {
            if (!object.Equals( kv.Value, secondValue ))
            {
                compared.Add( kv.Key, secondValue );
            }
      }
  }

Upvotes: 4

Related Questions