【问题标题】:Compare if two sequences are equal比较两个序列是否相等
【发布时间】:2016-03-06 22:05:56
【问题描述】:

在将其标记为重复之前,请考虑以下短程序:

static void Main()
{
    var expected = new List<long[]> { new[] { Convert.ToInt64(1), Convert.ToInt64(999999) } };
    var actual = DoSomething();
    if (!actual.SequenceEqual(expected)) throw new Exception();
}

static IEnumerable<long[]> DoSomething()
{
    yield return new[] { Convert.ToInt64(1), Convert.ToInt64(999999) };
}

我有一个方法可以返回一个 long 类型的数组序列。为了测试它,我编写了一些类似于Main 中的测试代码。

但是我得到了例外,但我不知道为什么。预期的序列不应该与实际返回的序列相媲美还是我错过了什么?

在我看来,方法和 epxected 都只包含一个包含 long 类型数组的单个元素,不是吗?

编辑:那么我如何实现不获取异常含义来比较枚举中的元素以返回相等?

【问题讨论】:

  • 有什么异常?
  • 序列中的元素是long[]。比较将是数组引用的比较,这确实是不同的。数组的元素(在序列中)不会被比较。
  • 您应该实现自己的比较器,并且应该将此比较器的实例作为第二个参数传递给 SequenceCompare。

标签: c# ienumerable yield


【解决方案1】:

SequenceEquals 测试序列中的元素是否相同。枚举中的元素是long[] 类型,所以我们实际上比较了两个不同的数组(但是包含相同的元素),这是通过比较它们的引用而不是它们的实际值来完成的。

所以我们在这里实际检查的是 expected[0] == actual[0] 而不是 expected[0].SequqnceEquals(actual[0])

这是 obiosuly 返回 false,因为两个数组共享不同的引用。

如果我们使用SelectMany 扁平化层次结构,我们会得到我们想要的:

if (!actual.SelectMany(x => x).SequenceEqual(expected.SelectMany(x => x))) throw new Exception();

编辑:

基于this approach,我找到了另一种优雅的方法来检查expected 中的所有元素是否也包含在actual 中:

if (!expected.All(x => actual.Any(y => y.SequenceEqual(x)))) throw new Exception();

这将搜索expected 中的任何子列表,如果actual 中有一个与当前列表顺序相同的列表。这似乎更聪明,因为我们不需要任何自定义EqualityComparer,也不需要奇怪的哈希码实现。

【讨论】:

    【解决方案2】:

    不,你的序列相等!

    让我们去掉序列位,只取每个项目的第一个元素中的内容

    var firstExpected = new[] { Convert.ToInt64(1), Convert.ToInt64(999999) };
    var firstActual = new[] { Convert.ToInt64(1), Convert.ToInt64(999999) };
    Console.WriteLine(firstExpected == firstActual); // writes "false"
    

    上面的代码比较两个独立的数组是否相等。 Equality 不检查数组的内容,它检查引用是否相等。

    您使用SequenceEquals 的代码本质上是在做同样的事情。它检查枚举中每个元素在每种情况下的引用。

    【讨论】:

    • 是的,实际上在一秒钟前就发现了。但是我的解决方案对我来说似乎并不方便,您有更好的解决方案吗?
    • @Yuval 的答案包含正确的方法。没必要我重写那个。
    【解决方案3】:

    实际的问题是您正在比较两个long[],而Enumerable.SequenceEquals 将使用ObjectEqualityComparer&lt;Int64[]&gt;(您可以通过检查EqualityComparer&lt;long[]&gt;.Default 来查看Enumerable.SequenceEquals 内部使用的内容) ),它将比较这两个数组的引用,而不是存储在数组中的实际 ,这显然不一样。

    要解决这个问题,您可以编写自定义EqualityComparer&lt;long[]&gt;

    static void Main()
    {
        var expected = new List<long[]> 
                           { new[] { Convert.ToInt64(1), Convert.ToInt64(999999) } };
        var actual = DoSomething();
    
        if (!actual.SequenceEqual(expected, new LongArrayComparer()))
            throw new Exception();
    }
    
    public class LongArrayComparer : EqualityComparer<long[]>
    {
        public override bool Equals(long[] first, long[] second)
        {
            return first.SequenceEqual(second);
        }
    
        // GetHashCode implementation in the courtesy of @JonSkeet
        // from http://stackoverflow.com/questions/7244699/gethashcode-on-byte-array
        public override int GetHashCode(long[] arr)
        {
            unchecked
            {
                if (array == null)
                {
                    return 0;
                }
    
                int hash = 17;
                foreach (long element in arr)
                {
                    hash = hash * 31 + element.GetHashCode();
                }
    
                return hash;
            }
        }
    }
    

    【讨论】:

    • 您的GetHashCode 不正确。它可以很好地用于这种特殊用途,因为SequenceEqual 不使用GetHashCode,但如果有人将它用于EqualityComparer&lt;long[]&gt; 的其他用途,例如Distinct,那将是不正确的。您需要生成与序列相等性相关的哈希码。
    • @JonHanna 你说得对,这是一个快速破解,只是为了表明可以轻松实现自定义比较器。我会解决的。
    • 多么幸运,几个月前我实现了一个通用的EqualityComparer,而我可以使用 lambda 表达式来实现这两种方法。不过,我现在基本上会完全使用这种方法,谢谢。
    • 是的。快速没有什么害处,但值得指出的是,以防有人将其复制粘贴到它不起作用的地方。
    • 代码是为程序员而不是计算机编写的。因此,不寻常的代码应该做一些不寻常的事情,否则下一个查看它的人将徒劳地浪费时间试图弄清楚发生了什么。在这种情况下,未检查块内的不需要的代码会迫使阅读器扫描整个块以查找原因。并且稍后有人的修改也可能不小心在该块中添加了更多代码。这是关于信噪比的。这是关于保持“这是奇怪的代码”的表面积很小。
    猜你喜欢
    • 2013-08-18
    • 1970-01-01
    • 2021-09-14
    • 2023-03-11
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    • 2014-09-20
    相关资源
    最近更新 更多