Тестирование на равенство между словарями в c # - PullRequest
54 голосов
/ 27 сентября 2010

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

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

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

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

Ответы [ 9 ]

91 голосов
/ 27 сентября 2010
dic1.Count == dic2.Count && !dic1.Except(dic2).Any();
12 голосов
/ 27 сентября 2010

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

Этот метод проверяет, что два словаря содержат одинаковые ключи с одинаковыми значениями (при условии, что оба словаря используют одну и ту же реализацию 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 голосов
/ 27 сентября 2010

Вы можете использовать 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]));
}
1 голос
/ 21 августа 2018

В дополнение к ответу @Nick Jones вам необходимо реализовать gethashcode таким же образом, без учета порядка. Я бы предложил что-то вроде этого:

public override int GetHashCode()
{
        int hash = 13;
        var orderedKVPList = this.DictProp.OrderBy(kvp => kvp.key)
        foreach (var kvp in orderedKVPList)
        {
                 hash = (hash * 7)  + kvp.Key.GetHashCode();
                 hash = (hash * 7)  + kvp.value.GetHashCode();
        }
        return hash;
}
1 голос
/ 29 июня 2015

Я думал, что принятый ответ будет правильным, основываясь на том, что я читал в смарт-справке для метода 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 не работают должным образом. Интересно, является ли это результатом того, что Except использует ленивую загрузку или способ, которым компаратор настроен для словаря. (Хотя, глядя на структуру и справочные объяснения, можно предположить, что).

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

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"}}};     
1 голос
/ 27 сентября 2010

@ ответ Аллена :

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

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

0 голосов
/ 27 марта 2015

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

0 голосов
/ 28 мая 2012

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

Я что-то упускаю или отмеченный ответ https://stackoverflow.com/a/3804852/916121 проверяет толькоравенство размера и ключей, но не их значения?

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

0 голосов
/ 27 сентября 2010

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

...