web-dev-qa-db-ja.com

C#での辞書間の同等性のテスト

辞書のキーと値に等号とハッシュメソッドが正しく実装されていると仮定すると、2つの辞書の等号をテストする最も簡潔で効率的な方法は何ですか?

この文脈では、同じキーのセット(順序は重要ではありません)が含まれている場合、2つの辞書は等しいと言われ、そのようなキーごとに値に同意します。

ここに私が思いついたいくつかの方法があります(おそらくもっとたくさんあります):

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()));
}
55
rony l
dic1.Count == dic2.Count && !dic1.Except(dic2).Any();
90
Nick Jones

それは本当に平等という意味に依存します。

このメソッドは、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;
}
13
LukeH

キー/値の比較に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]));
}
3
Lee

@ Allenの答え

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

は配列に関するものですが、IEnumerable<T>メソッドが使用されます。Dictionary<K,V>も。

1
abatishchev

私は、Exceptメソッドのスマートヘルプで読んでいたものに基づいて、受け入れられた答えが正しいと思いました:「デフォルトの等値比較子を使用して値を比較することにより、2つのシーケンスの差を生成します。」しかし、私はそれが良い答えではないことを発見しました。

次のコードを検討してください。

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の呼び出しも適切に機能しません。遅延読み込みを使用した例外の結果なのか、ディクショナリ用の比較器の設定方法なのか疑問に思います。 (ただし、構造と参照の説明を見ると、そうすべきであることが示唆されます。)

これが私が思いついた解決策です。使用した規則は次のとおりです。両方のキーが同じキーを含み、各キーの値が一致する場合、2つの辞書は等しくなります。キーと値の両方が同じ順序である必要があります。そして、私のソリューションは、キーのセット全体を反復処理することに依存しているため、最も効率的ではないかもしれません。

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

NewDictに対して以下を使用して、DictionaryEqual_NoSortを確認します(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
Machtyn

@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
Jason Masters

2つのディクショナリに同じキーが含まれているが順序が異なる場合、それらは等しいと見なされるべきですか?そうでない場合は、両方の列挙子を同時に実行して、辞書を比較する必要があります。これはおそらく、1つの辞書を列挙して、他の辞書の各要素を検索するよりも高速です。同等の辞書の要素が同じ順序になるという事前知識がある場合は、そのような二重列挙がおそらく道です。

0
supercat

OPの質問では、同等性テストはキーの一致だけでなく、その値「もカバーすべきである」と述べました。このコンテキストでは、2つの辞書は同じキーのセットを含む場合に等しいと言われます(順序は重要ではありません)、およびそのようなキーごとに、値に同意します。 "

私は何かを見逃していますか、マークされた答えをしますか https://stackoverflow.com/a/3804852/916121 サイズとキーが等しいかどうかだけをチェックし、値はチェックしませんか?

回答の横にこれを投稿しましたが、コメントとして追加する方法がわかりませんでした、ごめんなさい。

0
Access IT

入れ子になった辞書とリストについては、ここでいくつかのアイデアを組み合わせてこれを作成しました: https://Gist.github.com/NullVoxPopuli/f95baaa48b4e9854dcfe (ここに投稿するには多すぎるコード)〜100行

0
NullVoxPopuli