比较两个List<>的最快方法

时间:2012-10-09 08:29:50

标签: c# linq list

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

  1. 显示在第一个列表但不显示在第二个
  2. 中的项目
  3. 显示在第二个列表中但不在第一个列表中的项目
  4. 目前我正在使用List或IReadOnlyCollection并在linq查询中解决此问题:

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

    但这并不像我想的那样好。 因为我需要处理大量的列表,所以想要更快,更少资源吗?

13 个答案:

答案 0 :(得分:370)

使用Except

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

我怀疑有些方法实际上会略微快于此,但即便如此,远远超过你的O(N * M)方法。

如果要组合这些,可以使用上面的方法创建一个方法,然后使用return语句:

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

答案 1 :(得分:35)

效率更高的是使用Enumerable.Except

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

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

var first10 = inListButNotInList2.Take(10);

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

答案 2 :(得分:8)

如果您希望结果不区分大小写,则以下内容将起作用:

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

答案 3 :(得分:5)

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

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

答案 4 :(得分:2)

尝试这种方式:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));

答案 5 :(得分:1)

我用这段代码来比较两个有百万条记录的列表。

此方法不会花费太多时间

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

答案 6 :(得分:1)

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。为了正确实现IEquatableEqualsGetHashCode 必须以逻辑兼容的方式对实例的属性进行操作。

具有相同内容的两个列表仍然是不同的对象,并且将产生不同的哈希码。由于我们希望将这两个列表视为相等,因此必须让GetHashCode为它们每个生成相同的值。我们可以通过将哈希码委派给列表中的每个元素,然后使用标准的按位XOR来将它们组合在一起来完成此操作。 XOR与订单无关,因此列表的排序方式不同无关紧要。它们只包含等效成员,这仅是重要的事情。

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

答案 7 :(得分:1)

虽然 Jon Skeet 的回答是一个极好的建议,适用于少量到中等数量的元素(最多几百万)的日常练习,但它并不是最快的方法,而且资源效率也不是很高。一个明显的缺点是,要获得完整的差异需要对数据进行两次传递(如果对相等的元素也感兴趣,甚至可以传递三次)。显然,通过自定义重新实现 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 的结果如下:

<头>
方法 平均 错误 StdDev 比例 Gen 0 Gen 1 第 2 代 已分配
DiffLinq 115.19 毫秒 0.656 毫秒 0.582 毫秒 1.00 2800.0000 2800.0000 2800.0000 67110744 B
DiffZip 23.48 毫秒 0.018 毫秒 0.015 毫秒 0.20 - - - 720 B

对于 N = 100M:

<头>
方法 平均 错误 StdDev 比例 Gen 0 Gen 1 第 2 代 已分配
DiffLinq 12.146 秒 0.0427 s 0.0379 s 1.00 13000.0000 13000.0000 13000.0000 8589937032 B
DiffZip 2.324 秒 0.0019 s 0.0018 s 0.19 - - - 720 B

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

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

答案 8 :(得分:0)

如果仅需要组合结果,这也将起作用:

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

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

答案 9 :(得分:0)

  

Enumerable.SequenceEqual方法

     

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

Enumerable.SequenceEqual(list1, list2);

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

定义支持对象比较的方法,以实现相等性。

  

IEqualityComparer接口

     

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

答案 10 :(得分:-1)

可能很有趣,但对我有用

string.Join(&#34;&#34;,List1)!= string.Join(&#34;&#34;,List2)

答案 11 :(得分:-2)

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

var list3 = list1.Where(l => list2.ToList().Contains(l));

答案 12 :(得分:-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)