Подтвердить что ты не робот

Тестирование равенства словарей в С#

Предполагая, что словарные ключи и значения имеют правильные методы равенства и хэша, что является наиболее кратким и эффективным способом проверки равенства двух словарей?

В этом контексте два словаря называются равными, если они содержат один и тот же набор ключей (порядок не важен), и для каждого такого ключа они соглашаются на значение.

вот несколько способов, с которыми я пришел (возможно, еще много):

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

Ответ 1

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

Ответ 2

Это действительно зависит от того, что вы подразумеваете под равенством.

Этот метод будет проверять, что два словаря содержат одни и те же ключи с одинаковыми значениями (при условии, что оба словаря используют ту же реализацию IEqualityComparer<TKey>).

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

Ответ 3

Вы можете использовать linq для сравнения ключей/значений:

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

Ответ 4

@Allen answer:

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

относится к массивам, но по мере использования методов IEnumerable<T> он также может использоваться для Dictionary<K,V>.

Ответ 5

Я думал, что принятый ответ будет правильным, основываясь на том, что я читал в smarthelp для метода Except: "Производит заданное различие двух последовательностей, используя сопоставитель равенства по умолчанию для сравнения значений". Но я обнаружил, что это не очень хороший ответ.

Рассмотрим этот код:

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

В результате получается следующее:

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

Как вы можете видеть, оба "oldDict" и "newDict" настроены точно так же. И ни предлагаемое решение, ни призыв к SequenceEqual не работают должным образом. Интересно, является ли это результатом Исключения, используя ленивую загрузку или способ, которым сравнитель устанавливает для Словаря. (Хотя, глядя на структуру и пояснения, предположим, что это должно быть.)

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

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

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

и Сопоставления клавиш, но значение не соответствует (возвращает 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"}}};

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

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

Проверьте, не использует ли DictionaryEqual_NoSort следующее для newDict (DictionaryEquals_NoSort возвращает 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"}}};     

Ответ 6

Если два словаря содержат одни и те же ключи, но в другом порядке, должны ли они считаться равными? Если нет, то словари должны сравниваться путем запуска счетчиков через оба одновременно. Это, вероятно, будет быстрее, чем перечисление через один словарь и поиск каждого элемента в другом. Если у вас есть предварительное знание о том, что равные словари будут иметь свои элементы в одном порядке, такая двойная перепись, вероятно, является способом выхода.

Ответ 7

В вопросах OP он сказал, что тест равенства должен охватывать не только сопоставление ключей, но и их значение ". В этом контексте два словаря называются равными, если они содержат один и тот же набор ключей (порядок не важен), и для каждого такого ключа, они согласны с значением.

Я что-то упускаю или отмеченный ответ fooobar.com/questions/135072/... проверяет только на равенство размера и ключей, но не на их значение?

Я бы разместил это рядом с ответом, но не смог понять, как добавить его в качестве комментария, извините.

Ответ 8

Для вложенных словарей и списков я собрал пару идей, чтобы сделать это: https://gist.github.com/NullVoxPopuli/f95baaa48b4e9854dcfe (слишком много кода для публикации здесь ) ~ 100 строк