如何在C#中比较两个字典?

53

我有两个通用的字典。它们都有相同的键,但是它们的值可以不同。我想将第二个字典与第一个字典进行比较。如果它们的值有差异,我想将这些值存储在另一个字典中。

1st Dictionary
------------
key       Value

Barcode   1234566666
Price     20.00


2nd Dictionary
--------------
key       Value

Barcode   1234566666
Price     40.00


3rd Dictionary
--------------
key       Value

Price     40

有人能给我提供做这个的最佳算法吗?我写了一个算法,但是它有很多循环。我正在寻找一个简短高效的想法,比如使用LINQ查询表达式或LINQ lambda表达式的解决方案。我正在使用C#的.NET Framework 3.5。我发现了关于Except()方法的一些东西,但遗憾的是我无法理解该方法的作用。如果有人能解释一下建议的算法,那就太好了。


如果一个键在第一个字典中出现而不在第二个字典中,或者反过来,你想要做什么? - Jon Skeet
不,实际上键名和数量必须相同。在进入算法之前,我会使用iscontains()方法进行检查。提前感谢您。 - Thabo
14个回答

59

如果你已经检查了键是否相同,你可以直接使用:

var dict3 = dict2.Where(entry => dict1[entry.Key] != entry.Value)
                 .ToDictionary(entry => entry.Key, entry => entry.Value);
为了解释清楚,以下操作将会执行:
  • 遍历dict2中的键值对
  • 对于每个键值对,查找在dict1中的值,并过滤掉任何两个值相同的条目
  • 从剩下的条目(即dict1值不同的条目)中取出每个键值对的键和值,按照它们在dict2中的位置形成一个新的字典。

请注意,这样做避免了依赖于KeyValuePair<TKey, TValue>的相等性 - 虽然可能可以依赖它,但我个人认为这样更清晰。 (当您使用自定义相等比较器进行字典键比较时,它也适用-尽管您还需要将其传递给ToDictionary。)


53

尝试:

dictionary1.OrderBy(kvp => kvp.Key)
           .SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key))

1
这是在比较值吗?这将返回一个集合还是布尔值。 - Thabo
1
我尝试了几个不同的选项,其中字典键是字符串,这个选项似乎远远是最快的。 - Gene S
@Thabo 是的。SequenceEqual 使用了 KeyValuePair 的默认相等比较器,它会同时检查键和值。 - undefined

39

检查差异性:

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

以下代码返回所有不同的值。

dic1.Except(dic2) 

1
你能稍微解释一下这个吗? :) - Thabo
2
@Thabo:如果两个字典的大小相同,并且第一个字典中没有第二个字典中没有的元素,则这两个字典是等效的。第二行直接返回第一个字典中所有不在第二个字典中的元素。 - Chamika Sandamal
“.Except()” 是从哪里来的?我尝试使用 OrderedDictionary,但是出现了以下错误: Error CS1061 'OrderedDictionary' does not contain a definition for 'Except' and no accessible extension method 'Except' accepting a first argument of type 'OrderedDictionary' could be found (are you missing a using directive or an assembly reference?) - RichardB
1
@RichardB Linq。在文件顶部添加 using System.Linq;,然后再试一次。 - Jacob R

14

您提到两个字典具有相同的键,因此如果这个假设是正确的,您不需要任何复杂的操作:

        foreach (var key in d1.Keys)
        {
            if (!d1[key].Equals(d2[key]))
            {
                d3.Add(key, d2[key]);
            }
        }

或者我是否误解了您的问题?


你应该执行 !d1[key].Equals(d2[key]) - Saeed Amiri
哎呀...这很简单...我没想到这个,但有一个疑问,这样的性能好吗?因为我总是喜欢避免O(n)操作。 - Thabo
1
你缺少一个 !,因为你想要不同的值,而不是相等的值。 - Joachim Isaksson
@Thabo 是的,这接近于O(n)操作。由于它必须至少比较所有值一次,因此O(n)基本上是您可以达到的最低限度。 - Joachim Isaksson

5
假设两个字典具有相同的键,最简单的方法是:
var result = a.Except(b).ToDictionary(x => x.Key, x => x.Value);

编辑

请注意a.Except(b)b.Except(a)给出不同的结果:

a.Except(b): Price     20
b.Except(a): Price     40

如果两个字典的键的顺序不同,那会怎样?这个能行吗? - Thabo
1
@Thabo 是的。但请注意,a.Except(b) 的结果与 b.Except(a) 不同。 - Adi Lester

4
你应该能够加入它们的键并选择两个值。然后,你可以根据值是相同还是不同进行过滤。最后,你可以使用键和第二个值将集合转换为字典。
  var compared = first.Join( second, f => f.Key, s => s.Key, (f,s) => new { f.Key, FirstValue = f.Value, SecondValue = s.Value } )
                      .Where( j => j.FirstValue != j.SecondValue )
                      .ToDictionary( j => j.Key, j => j.SecondValue );

使用循环也不会太差。我怀疑它们的性能特性相似。

  var compared = new Dictionary<string,object>();
  foreach (var kv in first)
  {
      object secondValue;
      if (second.TryGetValue( kv.Key, out secondValue ))
      {
            if (!object.Equals( kv.Value, secondValue ))
            {
                compared.Add( kv.Key, secondValue );
            }
      }
  }

4
var diff1 = d1.Except(d2);
var diff2 = d2.Except(d1);
return diff1.Concat(diff2);

编辑: 如果您确定所有的键都是相同的,您可以执行以下操作:

var diff = d2.Where(x=>x.Value != d1[x.Key]).ToDictionary(x=>x.Key, x=>x.Value);

这并不会产生一个字典,而是使用IEnumerable<KeyValuePair<K,V>> - Adi Lester
1
对于第一个,我们不能使用字典,但是对于第二个,我会更新我的答案。 - Saeed Amiri

1

在最近的C#版本中,您可以尝试

        public static Dictionary<TK, TV> ValueDiff<TK, TV>(this Dictionary<TK, TV> dictionary,
            Dictionary<TK, TV> otherDictionary)
        {
            IEnumerable<(TK key, TV otherValue)> DiffKey(KeyValuePair<TK, TV> kv)
            {
                var otherValue = otherDictionary[kv.Key];
                if (!Equals(kv.Value, otherValue))
                {
                    yield return (kv.Key, otherValue);
                }
            }

            return dictionary.SelectMany(DiffKey)
                .ToDictionary(t => t.key, t => t.otherValue, dictionary.Comparer);
        }

我不确定SelectMany总是最快的解决方案,但这是一种只选择相关项并在同一步骤中生成结果条目的方法。遗憾的是,C#不支持在lambda表达式中使用yield return,虽然我可以构造单个或没有项的集合,但我选择使用内部函数。
哦,正如你所说,如果键相同,可能可以对它们进行排序。然后您可以使用Zip
        public static Dictionary<TK, TV> ValueDiff<TK, TV>(this Dictionary<TK, TV> dictionary,
            Dictionary<TK, TV> otherDictionary)
        {
            return dictionary.OrderBy(kv => kv.Key)
                .Zip(otherDictionary.OrderBy(kv => kv.Key))
                .Where(p => !Equals(p.First.Value, p.Second.Value))
                .ToDictionary(p => p.Second.Key, p => p.Second.Value, dictionary.Comparer);
        }

个人而言,我倾向于不使用Linq,而是像carlosfigueira和vanfosson一样使用简单的foreach

        public static Dictionary<TK, TV> ValueDiff2<TK, TV>(this Dictionary<TK, TV> dictionary,
            Dictionary<TK, TV> otherDictionary)
        {
            var result = new Dictionary<TK, TV>(dictionary.Count, dictionary.Comparer);
            foreach (var (key, value) in dictionary)
            {
                var otherValue = otherDictionary[key];
                if (!Equals(value, otherValue))
                {
                    result.Add(key, otherValue);
                }
            }

            return result;
        }

如果有值之间的差异,我想将这些值存储在单独的字典中。 - Theodor Zoulias
@TheodorZoulias 我可能有点累了,我重新写了我的答案。我来这里是通过谷歌搜索的,似乎根本没有读懂问题。抱歉! - TheConstructor

1

如果您需要对两者进行全面比较(考虑Venn图表结果),则对于给定的键,假设它至少存在于字典中之一,则有四个离散的结果

public enum KeyCompareResult
{
  ValueEqual,
  NotInLeft,
  NotInRight,
  ValueNotEqual,
}

要获取字典中的所有键,请使用 dictionary.Keys。要获取任一字典中的键集,请使用 Enumerable.Union,它将合并集合并过滤重复项。
假设您想要一些更通用的方法,那么您可以编写以下比较:
public IEnumerable<KeyValuePair<TKey, KeyCompareResult>> GetDifferences<TKey, TValue>(
    IDictionary<TKey, TValue> leftDict,
    IDictionary<TKey, TValue> rightDict,
    IEqualityComparer<TValue> comparer = null)
{
    var keys = leftDict.Keys.Union(rightDict.Keys);
    comparer ??= EqualityComparer<TValue>.Default;
    return keys.Select(key =>
    {
        if (!leftDict.TryGetValue(key, out var left))
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.NotInLeft);
        }
        else if (!rightDict.TryGetValue(key, out var right))
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.NotInRight);
        }
        else if (!comparer.Equals(left, right))
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.ValueNotEqual);
        }
        else
        {
            return KeyValuePair.Create<TKey, KeyCompareResult>(key, KeyCompareResult.ValueEqual);
        }
    });
}

左/右的区别不明显,除非您查看方法调用,因此,您可能希望将方法结果选择为消息或其他更有意义的数据结构

var left = new Dictionary<int, string> { { 1, "one" }, { 2, "two" }, { 4, "four" } };
var right = new Dictionary<int, string> { { 1, "one" }, { 2, "A different value" }, { 3, "three" } };

GetDifferences(left, right, StringComparer.InvariantCulture)
    .Display(); // or however you want to process the data
/*
Key Value
--- -------------
  1 ValueEqual 
  2 ValueNotEqual 
  4 NotInRight 
  3 NotInLeft 
*/

1
我会通过以下方式比较两个字典:
Dictionary<string, string> dict1 = new Dictionary<string, string>()
{
    {"1","1" },
    {"2","1" },
    {"3","3" },
};

Dictionary<string, string> dict2 = new Dictionary<string, string>()
{
    {"1","1" },
    {"3","3" },
    {"2","1" },
};

var areEqaul = dict1.Count == dict2.Count && !dict1.Keys.Any(key => !dict2.Keys.Contains(key)) &&
    !dict1.Keys.Any(key => dict2[key] != dict1[key]));

基本上,我会检查键是否相同,如果键相同,则检查两个字典中相应键的值


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接