在 C # 中测试字典之间的相等性

假设字典键和值的 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()));
}
44630 次浏览

这实际上取决于你所说的平等是什么意思。

此方法将测试两个字典是否包含具有相同值的相同键(假设两个字典使用相同的 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;
}
dic1.Count == dic2.Count && !dic1.Except(dic2).Any();

您可以使用 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]));
}

如果两本字典包含相同的键,但是按不同的顺序排列,那么它们应该被认为是相等的吗?如果没有,那么应该通过同时运行两个枚举器来比较字典。这可能比通过一个字典枚举并在另一个字典中查找每个元素要快。如果您事先知道,相等的字典将以相同的顺序列出它们的元素,那么这样的双枚举可能是正确的方法。

我认为接受的答案将是正确的,基于我正在阅读的智能帮助的除法: “产生设置差异的两个序列使用默认的相等比较器比较值。”但我发现这不是个好答案。

考虑下面的代码:

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

正如您可以看到的,“ oldDect”和“ newDect”的设置完全相同。不管是建议的解决方案还是对 Sequenceequals 的调用都不能正常工作。我想知道这是否是由于使用延迟加载或比较器设置为 Dictionary 的方式所致。(不过,从结构和参考解释来看,应该是这样。)

这是我想到的解决办法。请注意,我使用的规则如下: 如果两个字典包含相同的键和每个键的值匹配,则两个字典是相等的。键和值必须按相同的顺序排列。我的解决方案可能不是最有效的,因为它依赖于遍历整个键集。

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

键顺序匹配,但 Value 不匹配(返回 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;
}

检查 DictionaryEquals _ Nosort 是否使用 newDect 的以下内容(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"}}};

除了@Nick Jones 的回答之外,您还需要以相同的顺序不可知的方式实现 gethashcode。我的建议是这样的:

public override int GetHashCode()
{
var 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;
}

简单的 O (N)时间,O (1)空间解决方案与空检查

使用 Set 操作 IntersectUnionExcept的其他解决方案都很好,但是这些解决方案需要额外的 O(N)内存来生成最终的结果字典,该结果字典只用于计数元素。

相反,使用 Linq数不胜数来检查这一点。首先验证两个字典的计数,然后迭代 所有 D1的键值对,并检查它们是否等于 D2的键值对。注:Linq 确实为集合迭代器分配了内存,但它是集合大小 -O (1)空间的不变量。TryGetValue分期付款复杂度为 O (1)。

// KV is KeyValue pair
var areDictsEqual = d1.Count == d2.Count && d1.All(
(d1KV) => d2.TryGetValue(d1KV.Key, out var d2Value) && (
d1KV.Value == d2Value ||
d1KV.Value?.Equals(d2Value) == true)
);
  • 为什么是 d1KV.Value == d2Value?- 这是为了检查对象引用是否相等。此外,如果两者都是 nulld1KV.Value == d2Value将评估为 true

  • 为什么是 d1Kv.Value?.Equals(d2Value) == true?- Value?.用于空安全检查,.Equals用于基于对象的 Equals 和 HashCode 方法测试两个对象的相等性。

您可以随意调整相等检查。我假设 Dect 值是 nullable类型,以使解决方案更通用(例如: string, int?, float?)。如果是非空类型,则可以简化检查。


最后注意: 在 C # 字典中,键 不行为空。但值可以为空。