278

比较两个大型(> 50.000 个项目)的最快(和最少的资源密集型)是什么,因此有两个列表,如下所示:

  1. 出现在第一个列表中但不在第二个列表中的项目
  2. 显示在第二个列表中但不在第一个列表中的项目

目前我正在使用 List 或 IReadOnlyCollection 并在 linq 查询中解决此问题:

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

但这并没有我想要的那么好。由于我需要处理大量列表,因此有什么想法可以使这更快,更省资源吗?

4

16 回答 16

549

使用Except

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

我怀疑有些方法实际上会比这快一点,但即使这样也会你的 O(N * M) 方法快得多。

如果你想结合这些,你可以用上面的方法创建一个方法,然后是一个 return 语句:

return !firstNotSecond.Any() && !secondNotFirst.Any();

需要注意的一点是,问题中的原始代码与此处的解决方案之间的结果存在差异:仅在一个列表中的任何重复元素将仅在我的代码中报告一次,而它们将被报告为多次它们出现在原始代码中的时间。

例如,对于 和 的列表,[1, 2, 2, 2, 3]原始[1]代码中的“list1 中的元素但不是 list2 中的元素”结果将是[2, 2, 2, 3]. 用我的代码就可以了[2, 3]。在许多情况下,这不是问题,但值得注意。

于 2012-10-09T08:31:12.910 回答
83

Enumerable.SequenceEqual 方法

根据相等比较器确定两个序列是否相等。 MS.Docs

Enumerable.SequenceEqual(list1, list2);

这适用于所有原始数据类型。如果您需要在需要实现的自定义对象上使用它IEqualityComparer

定义支持比较对象是否相等的方法。

IEqualityComparer 接口

定义支持比较对象是否相等的方法。 IEqualityComparer 的 MS.Docs

于 2019-05-10T14:51:28.200 回答
42

更有效的是使用Enumerable.Except

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

该方法是通过使用延迟执行来实现的。这意味着您可以编写例如:

var first10 = inListButNotInList2.Take(10);

它也很有效,因为它在内部使用 aSet<T>来比较对象。它的工作原理是首先从第二个序列中收集所有不同的值,然后流式传输第一个序列的结果,检查它们以前没有见过。

于 2012-10-09T08:31:44.763 回答
10

如果您希望结果不区分大小写,则可以使用以下方法:

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecond将包含b1.dll

secondNotFirst将包含b2.dll

于 2016-01-20T16:32:47.327 回答
5
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

有时您只需要知道两个列表是否不同,而不需要知道这些差异是什么。在这种情况下,请考虑将此扩展方法添加到您的项目中。请注意,您列出的对象应实现 IEquatable!

用法:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

无论Component类是什么,这里显示的方法Car都应该几乎相同地实现。

注意我们是如何编写 GetHashCode 的,这一点非常重要。为了正确实现IEquatable必须以逻辑兼容的方式对实例的属性进行操作EqualsGetHashCode

两个具有相同内容的列表仍然是不同的对象,并且会产生不同的哈希码。由于我们希望这两个列表被视为相等,因此我们必须GetHashCode为它们中的每一个生成相同的值。我们可以通过将哈希码委托给列表中的每个元素,并使用标准的按位异或来组合它们来实现这一点。XOR 与顺序无关,因此列表的排序方式是否不同并不重要。重要的是它们只包含等效的成员。

注意:这个奇怪的名字是暗示该方法不考虑列表中元素的顺序。如果您确实关心列表中元素的顺序,那么此方法不适合您!

于 2018-12-15T11:31:30.897 回答
4

不是为了这个问题,但这里有一些代码来比较列表是否相等!相同的对象:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}
于 2014-01-20T14:26:52.527 回答
3

试试这种方式:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));
于 2012-10-09T08:32:30.580 回答
3

如果只需要组合结果,这也可以:

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

其中 T 是列表元素的类型。

于 2018-09-12T10:07:56.540 回答
2

我已经使用此代码比较了两个具有数百万条记录的列表。

这种方法不会花太多时间

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }
于 2016-08-09T07:35:35.163 回答
1

虽然 Jon Skeet 的回答对于日常使用少量到中等数量的元素(最多几百万)的练习来说是一个很好的建议,但它并不是最快的方法,也不是非常有效的资源。一个明显的缺点是,要获得完整的差异,需要对数据进行两次传递(如果相同的元素也很感兴趣,即使是 3 次)。显然,可以通过自定义重新实现该Except方法来避免这种情况,但仍然存在哈希集的创建需要大量内存并且哈希的计算需要时间。

对于非常大的数据集(数十亿个元素),考虑特定情况通常是值得的。以下是一些可能会提供一些启发的想法: 如果可以比较元素(在实践中几乎总是如此),那么对列表进行排序并应用以下 zip 方法是值得考虑的:

/// <returns>The elements of the specified (ascendingly) sorted enumerations that are
/// contained only in one of them, together with an indicator,
/// whether the element is contained in the reference enumeration (-1)
/// or in the difference enumeration (+1).</returns>
public static IEnumerable<Tuple<T, int>> FindDifferences<T>(IEnumerable<T> sortedReferenceObjects,
    IEnumerable<T> sortedDifferenceObjects, IComparer<T> comparer)
{
    var refs  = sortedReferenceObjects.GetEnumerator();
    var diffs = sortedDifferenceObjects.GetEnumerator();
    bool hasNext = refs.MoveNext() && diffs.MoveNext();
    while (hasNext)
    {
        int comparison = comparer.Compare(refs.Current, diffs.Current);
        if (comparison == 0)
        {
            // insert code that emits the current element if equal elements should be kept
            hasNext = refs.MoveNext() && diffs.MoveNext();

        }
        else if (comparison < 0)
        {
            yield return Tuple.Create(refs.Current, -1);
            hasNext = refs.MoveNext();
        }
        else
        {
            yield return Tuple.Create(diffs.Current, 1);
            hasNext = diffs.MoveNext();
        }
    }
}

例如,这可以通过以下方式使用:

const int N = <Large number>;
const int omit1 = 231567;
const int omit2 = 589932;
IEnumerable<int> numberSequence1 = Enumerable.Range(0, N).Select(i => i < omit1 ? i : i + 1);
IEnumerable<int> numberSequence2 = Enumerable.Range(0, N).Select(i => i < omit2 ? i : i + 1);
var numberDiffs = FindDifferences(numberSequence1, numberSequence2, Comparer<int>.Default);

在我的计算机上进行基准测试给出了 N = 1M 的以下结果:

方法 意思是 错误 标准差 比率 0代 第一代 第 2 代 已分配
DiffLinq 115.19 毫秒 0.656 毫秒 0.582 毫秒 1.00 2800.0000 2800.0000 2800.0000 67110744乙
区分邮编 23.48 毫秒 0.018 毫秒 0.015 毫秒 0.20 - - - 720乙

对于 N = 100M:

方法 意思是 错误 标准差 比率 0代 第一代 第 2 代 已分配
DiffLinq 12.146 秒 0.0427 秒 0.0379 秒 1.00 13000.0000 13000.0000 13000.0000 8589937032 乙
区分邮编 2.324 秒 0.0019 秒 0.0018 秒 0.19 - - - 720乙

请注意,此示例当然受益于列表已经排序并且可以非常有效地比较整数这一事实。但这正是重点:如果您确实有有利的环境,请确保利用它们。

一些进一步的评论:比较功能的速度显然与整体性能相关,因此对其进行优化可能是有益的。这样做的灵活性是压缩方法的一个好处。此外,并行化对我来说似乎更可行,尽管绝不容易,而且可能不值得付出努力和开销。然而,将过程加速大约 2 倍的简单方法是将列表分别分成两半(如果可以有效地完成)并并行比较各个部分,一个从前到后处理,另一个处理以相反的顺序。

于 2021-03-20T16:52:17.250 回答
0

我比较了 3 种不同的方法来比较不同的数据集。下面的测试创建了一个包含从0to的所有数字的字符串集合length - 1,然后是另一个具有相同范围但具有偶数的集合。然后我从第一个集合中挑选出奇数。

使用 Linq 除外

public void TestExcept()
{
    WriteLine($"Except {DateTime.Now}");
    int length = 20000000;
    var dateTime = DateTime.Now;
    var array = new string[length];
    for (int i = 0; i < length; i++)
    {
        array[i] = i.ToString();
    }
    Write("Populate set processing time: ");
    WriteLine(DateTime.Now - dateTime);
    var newArray = new string[length/2];
    int j = 0;
    for (int i = 0; i < length; i+=2)
    {
        newArray[j++] = i.ToString();
    }
    dateTime = DateTime.Now;
    Write("Count of items: ");
    WriteLine(array.Except(newArray).Count());
    Write("Count processing time: ");
    WriteLine(DateTime.Now - dateTime);
}

输出

Except 2021-08-14 11:43:03 AM
Populate set processing time: 00:00:03.7230479
2021-08-14 11:43:09 AM
Count of items: 10000000
Count processing time: 00:00:02.9720879

使用 HashSet.Add

public void TestHashSet()
{
    WriteLine($"HashSet {DateTime.Now}");
    int length = 20000000;
    var dateTime = DateTime.Now;
    var hashSet = new HashSet<string>();
    for (int i = 0; i < length; i++)
    {
        hashSet.Add(i.ToString());
    }
    Write("Populate set processing time: ");
    WriteLine(DateTime.Now - dateTime);
    var newHashSet = new HashSet<string>();
    for (int i = 0; i < length; i+=2)
    {
        newHashSet.Add(i.ToString());
    }
    dateTime = DateTime.Now;
    Write("Count of items: ");
    // HashSet Add returns true if item is added successfully (not previously existing)
    WriteLine(hashSet.Where(s => newHashSet.Add(s)).Count());
    Write("Count processing time: ");
    WriteLine(DateTime.Now - dateTime);
}

输出

HashSet 2021-08-14 11:42:43 AM
Populate set processing time: 00:00:05.6000625
Count of items: 10000000
Count processing time: 00:00:01.7703057

特殊 HashSet 测试

public void TestLoadingHashSet()
{
    int length = 20000000;
    var array = new string[length];
    for (int i = 0; i < length; i++)
    {
       array[i] = i.ToString();
    }
    var dateTime = DateTime.Now;
    var hashSet = new HashSet<string>(array);
    Write("Time to load hashset: ");
    WriteLine(DateTime.Now - dateTime);
}
> TestLoadingHashSet()
Time to load hashset: 00:00:01.1918160

使用 .Contains

public void TestContains()
{
    WriteLine($"Contains {DateTime.Now}");
    int length = 20000000;
    var dateTime = DateTime.Now;
    var array = new string[length];
    for (int i = 0; i < length; i++)
    {
        array[i] = i.ToString();
    }
    Write("Populate set processing time: ");
    WriteLine(DateTime.Now - dateTime);
    var newArray = new string[length/2];
    int j = 0;
    for (int i = 0; i < length; i+=2)
    {
        newArray[j++] = i.ToString();
    }
    dateTime = DateTime.Now;
    WriteLine(dateTime);
    Write("Count of items: ");
    WriteLine(array.Where(a => !newArray.Contains(a)).Count());
    Write("Count processing time: ");
    WriteLine(DateTime.Now - dateTime);
}

输出

Contains 2021-08-14 11:19:44 AM
Populate set processing time: 00:00:03.1046998
2021-08-14 11:19:49 AM
Count of items: Hosting process exited with exit code 1.
(Didnt complete. Killed it after 14 minutes)

结论:

  • LinqExcept在我的设备上运行比使用HashSets(n=20,000,000) 慢了大约 1 秒。
  • 使用WhereContains运行了很长时间

HashSet 的结束语:

  • 唯一数据
  • 确保覆盖GetHashCode(正确)类类型
  • 如果您制作数据集的副本,可能需要多达 2 倍的内存,具体取决于实现
  • HashSet针对使用IEnumerable构造函数克隆其他 HashSet 进行了优化,但将其他集合转换为 HashSet 会更慢(参见上面的特殊测试)
于 2021-08-14T19:14:53.167 回答
0

第一种方法:

if (list1 != null && list2 != null && list1.Select(x => list2.SingleOrDefault(y => y.propertyToCompare == x.propertyToCompare && y.anotherPropertyToCompare == x.anotherPropertyToCompare) != null).All(x => true))
   return true;

如果您可以使用重复值,则第二种方法:

if (list1 != null && list2 != null && list1.Select(x => list2.Any(y => y.propertyToCompare == x.propertyToCompare && y.anotherPropertyToCompare == x.anotherPropertyToCompare)).All(x => true))
   return true;
于 2021-11-26T06:58:39.647 回答
-1

一条线:

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 1, 2, 3, 4 };
if (list1.Except(list2).Count() + list2.Except(list1).Count() == 0)
    Console.WriteLine("same sets");
于 2021-09-15T10:22:26.613 回答
-2

也许这很有趣,但这对我有用:

string.Join("",List1) != string.Join("", List2)
于 2017-04-07T14:06:36.543 回答
-2

我认为这是一种逐个元素比较两个列表的简单方法

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)
于 2019-01-15T07:47:56.180 回答
-3

这是您会找到的最佳解决方案

var list3 = list1.Where(l => list2.ToList().Contains(l));
于 2018-09-03T16:31:30.387 回答