C# Сравните два словаря для равенства



Я хочу сравнить в C# два словаря с as ключами a string и as значением списка ints. Я предполагаю, что два словаря равны, когда они оба имеют одинаковые ключи и для каждого ключа в качестве значения список с одинаковыми целыми числами (оба не обязательно в одинаковом порядке).



Я использую оба ответа из этого и этого связанного вопроса, но оба проваливают мой набор тестов для тестовых функций DoesOrderKeysMatter и DoesOrderValuesMatter.



Мой тестовый набор:



using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;


namespace UnitTestProject1
{
[TestClass]
public class ProvideReportTests
{
[TestMethod]
public void AreSameDictionariesEqual()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);

// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict1);

// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");

}

[TestMethod]
public void AreDifferentDictionariesNotEqual()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);

Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();

// act
bool dictsAreEqual = true;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);

// assert
Assert.IsFalse(dictsAreEqual, "Dictionaries are equal");

}

[TestMethod]
public void DoesOrderKeysMatter()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);

Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(3);
list3.Add(4);
dict2.Add("b", list3);
List<int> list4 = new List<int>();
list4.Add(1);
list4.Add(2);
dict2.Add("a", list4);

// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);

// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");

}

[TestMethod]
public void DoesOrderValuesMatter()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);

Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(2);
list3.Add(1);
dict2.Add("a", list3);
List<int> list4 = new List<int>();
list4.Add(4);
list4.Add(3);
dict2.Add("b", list4);

// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);

// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");

}


private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
return dict1.Keys.Count == dict2.Keys.Count &&
dict1.Keys.All(k => dict2.ContainsKey(k) && object.Equals(dict2[k], dict1[k]));

// also fails:
// return dict1.OrderBy(kvp => kvp.Key).SequenceEqual(dict2.OrderBy(kvp => kvp.Key));
}
}
}


Что такое правильно ли сравнивать такого рода словари? Или есть ошибка в моем (по общему признанию, неуклюже написанном) тесте?



Обновить



Я пытаюсь включить ответ Серви в свой набор тестов, как показано ниже, но я получаю некоторые ошибки (подчеркнутые красной волнистой линией в Visual Studio):





  • SetEquals в методе 'Equals сказано:" не содержит определения для SetEquals, принимающего первый аргумент типа Generic.Список.


  • В AreDictionariesEqual it saysDictionaryComparer> является типом,но используется в качестве переменной.'



    Пространство Имен UnitTestProject1
    {
    [класс TestClass]
    публичный класс ProvideReportTests
    {
    [TestMethod]
    // ... то же, что и выше



        private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
    {
    DictionaryComparer<string, List<int>>(new ListComparer<int>() dc = new DictionaryComparer<string, List<int>>(new ListComparer<int>();
    return dc.Equals(dict1, dict2);

    }

    }

    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();
    }
    }

    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();
    }
    }

    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);
    }


    }



704   9  

9 ответов:

Итак, сначала нам нужен компаратор равенства для словарей. Он должен убедиться, что у них есть соответствующие ключи и, если они есть, сравнить значения каждого ключа:

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();
    }
}
Но этого недостаточно само по себе. Нам нужно сравнить значения словаря, используя другой пользовательский компаратор, а не компаратор по умолчанию, поскольку компаратор списка по умолчанию не будет смотреть на значения списка:
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();
    }
}

Который использует следующий метод расширения:

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);
}

Теперь мы можем просто напишите:

new DictionaryComparer<string, List<int>>(new ListComparer<int>())
    .Equals(dict1, dict2);

Я знаю, что на этот вопрос уже есть приемлемый ответ, но я хотел бы предложить еще более простую альтернативу:

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

namespace Foo
{
    public static class DictionaryExtensionMethods
    {
        public static bool ContentEquals<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, Dictionary<TKey, TValue> otherDictionary)
        {
            return (otherDictionary ?? new Dictionary<TKey, TValue>())
                .OrderBy(kvp => kvp.Key)
                .SequenceEqual((dictionary ?? new Dictionary<TKey, TValue>())
                                   .OrderBy(kvp => kvp.Key));
        }
    }
}

Я думаю, что AreDictionariesEqual() просто нужен другой метод для сравнения списков

Так что если порядок записей не имеет значения, вы можете попробовать это:

  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]));

}

И если порядок записей имеет значение, попробуйте сделать следующее:

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])));         
}

Принятый ответ выше не всегда вернет правильное сравнение, потому что использование хэш-набора для сравнения 2 списков не будет учитывать повторяющиеся значения в списках. Например, если бы ОП имел:

var dict1 = new Dictionary<string, List<int>>() { { "A", new List<int>() { 1, 2, 1 } } };
var dict2 = new Dictionary<string, List<int>>() { { "A", new List<int>() { 2, 2, 1 } } };

Тогда результат словарного сравнения - они равны, когда их нет. Единственное решение, которое я вижу, - это отсортировать список 2 и сравнить значения по индексу, но я уверен, что кто-то умнее меня может придумать более эффективный способ.

Если известно, что два словаря используют эквивалентные реализации IEqualityComparer, и один хочет считать эквивалентными все ключи, которые эта реализация считает эквивалентными, они содержат одинаковое количество элементов, и один (произвольно выбранный) сопоставляет все ключи элементов, найденные в другом, соответствующим значениям из другого, они будут эквивалентны, Если или пока один из них не будет изменен. Тестирование для этих условий будет быстрее, чем любой подход, который не предполагает, что оба словаря используют один и тот же IEqualityComparer.

Если два словаря не используют одну и ту же реализацию IEqualityComparer, они обычно не должны считаться эквивалентными независимо от элементов, которые они содержат. Например, Dictionary<String,String> с чувствительным к регистру компаратором и один с нечувствительным к регистру компаратором, оба из которых содержат пару ключ-значение ("Fred", "Quimby") не эквивалентны, так как последний сопоставил бы "FRED" с "Quimby", а первый-нет.

Только в том случае, если словари используют такая же реализация IEqualityComparer, но если кто-то заинтересован в более точном определении равенства ключей, чем то, которое используется в словарях, и копия ключа не хранится с каждым значением, необходимо будет построить новый словарь для проверки исходных словарей на равенство. Возможно, лучше отложить этот шаг до тех пор, пока более ранний тест не покажет, что словари, по-видимому, совпадают. Затем постройте Dictionary<TKey,TKey>, который сопоставляет каждый ключ из одного словаря самому себе, а затем посмотрите все ключи другого словаря в этом, чтобы убедиться, что они соответствуют вещам, которые соответствуют. Если бы оба словаря использовали нечувствительные к регистру компараторы, и один содержал бы ("Fred", "Quimby"), а другой ("FRED", "Quimby"), новый временный словарь сопоставил бы "FRED" с "Fred", и сравнение этих двух строк показало бы, что словари не совпадают.

Вот способ использования Linq, вероятно, жертвуя некоторой эффективностью для аккуратного кода. Другой пример Linq изjfren484 фактически проваливает тест DoesOrderValuesMatter (), потому что он зависит от значения по умолчанию Equals() для List<int>, которое зависит от порядка.

private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
    string dict1string = String.Join(",", dict1.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));
    string dict2string = String.Join(",", dict2.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));

    return dict1string.Equals(dict2string);
}

Преобразуйте словарь в список KeyValuePair, а затем сравните как коллекции:

CollectionAssert.AreEqual(
   dict1.OrderBy(kv => kv.Key).ToList(),
   dict2.OrderBy(kv => kv.Key).ToList()
)

Мне нравится этот подход, потому что он дает больше деталей, когда тест не проходит

    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}'");
        }
    }
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;
    }

Comments

    Ничего не найдено.