C# Compare two dictionaries for equality

后端 未结 10 795
独厮守ぢ
独厮守ぢ 2020-12-01 10:41

I want to compare in C# two dictionaries with as keys a string and as value a list of ints. I assume two dictionaries to be equal when they both ha

相关标签:
10条回答
  • 2020-12-01 11:23

    I like this approach because it gives more details when the test fails

        public void AssertSameDictionary<TKey,TValue>(Dictionary<TKey,TValue> expected,Dictionary<TKey,TValue> actual)
        {
            string d1 = "expected";
            string d2 = "actual";
            Dictionary<TKey,TValue>.KeyCollection keys1= expected.Keys;
            Dictionary<TKey,TValue>.KeyCollection keys2= actual.Keys;
            if (actual.Keys.Count > expected.Keys.Count)
            {
                string tmp = d1;
                d1 = d2;
                d2 = tmp;
                Dictionary<TKey, TValue>.KeyCollection tmpkeys = keys1;
                keys1 = keys2;
                keys2 = tmpkeys;
            }
    
            foreach(TKey key in keys1)
            {
                Assert.IsTrue(keys2.Contains(key), $"key '{key}' of {d1} dict was not found in {d2}");
            }
            foreach (TKey key in expected.Keys)
            {
                //already ensured they both have the same keys
                Assert.AreEqual(expected[key], actual[key], $"for key '{key}'");
            }
        }
    
    0 讨论(0)
  • 2020-12-01 11:23
    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;
        }
    
    0 讨论(0)
  • 2020-12-01 11:24

    I think that AreDictionariesEqual() just needs another method for List comparison

    So if order of entries doesn't matter you can try this:

      static bool ListEquals(List<int> L1, List<int> L2)
    {
        if (L1.Count != L2.Count)
            return false;
    
        return L1.Except(L2).Count() == 0;
    }            
        /*
        if it is ok to change List content you may try
        L1.Sort();
        L2.Sort();
        return L1.SequenceEqual(L2);
        */
    
    
    static bool DictEquals(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
    {
        if (D1.Count != D2.Count)
            return false;
    
        return D1.Keys.All(k => D2.ContainsKey(k) && ListEquals(D1[k],D2[k]));
    
    }
    

    And if order of entries matters, try this:

    static bool DictEqualsOrderM(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
    {
        if (D1.Count != D2.Count)
            return false;
    
        //check keys for equality, than lists.           
        return (D1.Keys.SequenceEqual(D2.Keys) && D1.Keys.All(k => D1[k].SequenceEqual(D2[k])));         
    }
    
    0 讨论(0)
  • 2020-12-01 11:26

    Most of the answers are iterating the dictionaries multiple times while it should be simple:

        static bool AreEqual(IDictionary<string, string> thisItems, IDictionary<string, string> otherItems)
        {
            if (thisItems.Count != otherItems.Count)
            {
                return false;
            }
            var thisKeys = thisItems.Keys;
            foreach (var key in thisKeys)
            {
                if (!(otherItems.TryGetValue(key, out var value) &&
                      string.Equals(thisItems[key], value, StringComparison.OrdinalIgnoreCase)))
                {
                    return false;
                }
            }
            return true;
        }
    
    0 讨论(0)
  • 2020-12-01 11:28

    So first we need an equality comparer for dictionaries. It needs to ensure that they have matching keys and, if they do, compare the values of each key:

    public class DictionaryComparer<TKey, TValue> :
        IEqualityComparer<Dictionary<TKey, TValue>>
    {
        private IEqualityComparer<TValue> valueComparer;
        public DictionaryComparer(IEqualityComparer<TValue> valueComparer = null)
        {
            this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
        }
        public bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
        {
            if (x.Count != y.Count)
                return false;
            if (x.Keys.Except(y.Keys).Any())
                return false;
            if (y.Keys.Except(x.Keys).Any())
                return false;
            foreach (var pair in x)
                if (!valueComparer.Equals(pair.Value, y[pair.Key]))
                    return false;
            return true;
        }
    
        public int GetHashCode(Dictionary<TKey, TValue> obj)
        {
            throw new NotImplementedException();
        }
    }
    

    but this isn't enough on its own. We need to compare the values of the dictionary using another custom comparer, not the default comparer as the default list comparer won't look at the values of the list:

    public class ListComparer<T> : IEqualityComparer<List<T>>
    {
        private IEqualityComparer<T> valueComparer;
        public ListComparer(IEqualityComparer<T> valueComparer = null)
        {
            this.valueComparer = valueComparer ?? EqualityComparer<T>.Default;
        }
    
        public bool Equals(List<T> x, List<T> y)
        {
            return x.SetEquals(y, valueComparer);
        }
    
        public int GetHashCode(List<T> obj)
        {
            throw new NotImplementedException();
        }
    }
    

    Which uses the following extension method:

    public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second,
        IEqualityComparer<T> comparer)
    {
        return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
            .SetEquals(first);
    }
    

    Now we can simply write:

    new DictionaryComparer<string, List<int>>(new ListComparer<int>())
        .Equals(dict1, dict2);
    
    0 讨论(0)
  • 2020-12-01 11:31

    Convert the dictionary to a KeyValuePair list and then compare as collections:

    CollectionAssert.AreEqual(
       dict1.OrderBy(kv => kv.Key).ToList(),
       dict2.OrderBy(kv => kv.Key).ToList()
    );
    
    0 讨论(0)
提交回复
热议问题