【问题标题】:LINQ: Determine if two sequences contains exactly the same elementsLINQ:确定两个序列是否包含完全相同的元素
【发布时间】:2010-12-13 00:01:18
【问题描述】:

我需要确定两个集合是否包含完全相同的元素。顺序无关紧要。

例如,这两个数组应该被认为是相等的:

IEnumerable<int> data = new []{3, 5, 6, 9};
IEnumerable<int> otherData = new []{6, 5, 9, 3}

一个集合不能包含任何不在另一个集合中的元素。

这可以使用内置的查询运算符来完成吗?考虑到元素的数量可能从几个到数百不等,实现它的最有效方法是什么?

【问题讨论】:

  • 你认为{1,1,2}{1,2}“等价”序列吗?
  • @Mehrdad,是的,我希望那些被认为是平等的。
  • “集合”是指所有元素都是独一无二的?

标签: c# .net linq


【解决方案1】:

这是另一种方法:

IEnumerable<int> data = new[] { 3, 5, 6, 9 };
IEnumerable<int> otherData = new[] { 6, 5, 9, 3 };

data = data.OrderBy(d => d);
otherData = otherData.OrderBy(d => d);
data.Zip(otherData, (x, y) => Tuple.Create(x, y)).All(d => d.Item1 == d.Item2);

【讨论】:

  • Zip 将在到达任一列表的末尾时停止,这意味着 {1,2,3} {1,2,3,4} 将通过此方法比较“相等”。您可以在 Zip 之前检查长度,但也可以使用 SequenceEqual。
【解决方案2】:

如果要将数组视为“集合”并忽略顺序和重复项,可以使用HashSet&lt;T&gt;.SetEquals method

var isEqual = new HashSet<int>(first).SetEquals(second);

否则,您最好的选择可能是以相同的方式对两个序列进行排序并使用SequenceEqual 来比较它们。

【讨论】:

  • 我认为 HashSet.SetEquals 是我正在寻找的方法 :-)
  • 好答案——我忘记了 SetEquals!如果您可能有欺骗行为,那么在排序之前,您可能应该将序列复制到 List 中并首先比较长度——这样可以节省(昂贵的)排序或 SequenceEqual() 操作,以防长度不同。
  • @Justin Grant - 我不关注...您需要在比较长度之前删除重复项,这与排序一样昂贵。
  • 也许我理解错了,但这不也产生 O(n^2) 解决方案吗? HashSet ctor 被标记为 O(n) 并且 SetEquals 也因此被标记(如果相同的相等比较器用于 firstsecond)。
  • @JoshGust 这使得整个事情 O(2n) = O(n) 摊销而不是 O(n^2)。当然,这是假设一个好的散列函数。
【解决方案3】:

首先检查两个数据集合是否具有相同数量的元素,并检查一个集合中的所有元素是否都出现在另一个集合中

        IEnumerable<int> data = new[] { 3, 5, 6, 9 };
        IEnumerable<int> otherData = new[] { 6, 5, 9, 3 };

        bool equals = data.Count() == otherData.Count() && data.All(x => otherData.Contains(x));

【讨论】:

  • 对于一个常规数组,.Contains 是 O(N) 并且 .All 也是 O(N),使得这个 O(N^2)。如果您的数据集不是很小,那么基于排序和/或集合的选项会更好。
  • 如果输入中允许重复,也不会给出正确答案。
【解决方案4】:

如果您可能有重复项(或者如果您想要一个对较长列表表现更好的解决方案),我会尝试这样的事情:

static bool IsSame<T>(IEnumerable<T> set1, IEnumerable<T> set2)
{
    if (set1 == null && set2 == null)
        return true;
    if (set1 == null || set2 == null)
        return false;

    List<T> list1 = set1.ToList();
    List<T> list2 = set2.ToList();

    if (list1.Count != list2.Count)
        return false;

    list1.Sort();
    list2.Sort();

    return list1.SequenceEqual(list2);
}

更新:哎呀,你们是对的——下面的 except() 解决方案需要在过马路之前看两个方向。对于更长的列表,它的性能很差。忽略下面的建议! :-)

这是一种简单的方法。请注意,这假定列表没有重复项。

bool same = data.Except (otherData).Count() == 0;

【讨论】:

  • 您可以使用 .Any() 而不是 Count() - 这样它就不会枚举列表中的每个项目。
  • 如果data = {1,2}, otherData = {1,2,3} 怎么办?你也应该反过来检查。
  • 这在我的场景中不起作用,如果不按照 Kobi 的建议检查两种方式。如果有几百个元素,我会担心这种方法的性能。
  • @Kobi,当你可以先检查哪个更大然后再做除外时,为什么要同时检查两个方法
【解决方案5】:

我建议对两者进行排序,并逐个元素进行比较。

data.OrderBy(x => x).SequenceEqual(otherData.OrderBy(x => x))

我不确定OrderBy 的实现速度有多快,但如果它是 O(n log n) 排序,就像你期望的那样,总算法也是 O(n log n)。

对于某些数据情况,您可以通过使用 OrderBy 的自定义实现来改进这一点,例如使用计数排序,对于 O(n+k),其中 k 是值所在范围的大小。

【讨论】:

  • 那么O(n log n) 是比较非唯一序列的最佳复杂度吗?
【解决方案6】:
  1. 首先,检查长度。如果它们不同,则集合不同。
  2. 你可以data.Intersect(otherData);,检查长度是否相同。
  3. 或者,对集合进行简单排序,然后遍历它们。

【讨论】:

  • "data.Intersect(otherData),并检查长度是否相同" -- 你必须确保长度与其他两个序列相同
  • @Mark - 在第一步你应该检查它们的长度相同,所以你应该没问题。写的不是很好,我同意。 (另外,谈谈长尾……超过 2 年才能得到评论:)
  • 是的,这是真的。与仅从序列 1 创建 HashSet 并将其与序列 2 进行比较相比,检查长度(因为它是 IEnumerable)不一定可行。无论如何,Intersect 基本上都会这样做。
  • 我喜欢这种方法的简单性,但它使用 GetHashCode() 进行元素比较。
【解决方案7】:

这应该会有所帮助:

    IEnumerable<int> data = new []{ 3,5,6,9 };
    IEnumerable<int> otherData = new[] {6, 5, 9, 3};

    if(data.All(x => otherData.Contains(x)))
    {
        //Code Goes Here
    }

【讨论】:

  • 复杂度为 O(n²)。如果您的列表中有几十个以上的项目,则很危险。
  • 很简单,但这在我的场景中表现不够好。
  • 如果otherData 包含其他元素,则不会捕获。
猜你喜欢
  • 2010-11-07
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-28
  • 1970-01-01
相关资源
最近更新 更多