比较两个通用列表差异的最快方法


213

比较两个大型项目(> 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();

但这并没有我想要的那样好。有什么想法可以让我更快地处理和减少资源消耗,因为我需要处理很多列表?

Answers:


452

用途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],在“列表1中的元素,但不是列表2”导致原始代码将是[2, 2, 2, 3]。用我的代码就可以了[2, 3]。在许多情况下,这不是问题,但是值得一提。


8
这确实是巨大的性能提升!感谢您的回答。
弗兰克(Frank)

2
我想知道有两个庞大的清单,在比较之前进行排序是否有用?或在扩展方法内,传入的列表已经排序。
拉里

9
@Larry:没有排序;它建立一个哈希集。
乔恩·斯基特

2
@PranavSingh:它适用于任何具有适当相等性的东西-因此,如果您的自定义类型重写Equals(object)和/或实现,IEquatable<T>则应该没问题。
乔恩·斯基特

2
@ k2ibegin:它使用默认的相等比较器,它将使用IEquatable<T>实现或object.Equals(object)方法。听起来您应该使用最少的可重复示例创建一个新问题-我们无法真正诊断注释中的内容。
乔恩·斯基特

40

更有效的方法是使用Enumerable.Except

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

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

var first10 = inListButNotInList2.Take(10);

由于它内部使用a Set<T>比较对象,因此它也是有效的。它的工作方式是首先从第二个序列中收集所有不同的值,然后流式传输第一个结果,并检查它们之前是否未见过。


1
嗯 没有完全推迟。我会说部分推迟了。Set<T>从第二个序列构建一个完整的对象(即,它已完全迭代并存储),然后产生可以从第一个序列添加的项目。
支出者2012年

2
@spender,这就像说Where部分推迟了执行,因为list.Where(x => x.Id == 5)数字的值5存储在开头,而不是懒惰地执行。
jwg 2014年

26

Enumerable.SequenceEqual方法

根据相等比较器确定两个序列是否相等。 微软文档

Enumerable.SequenceEqual(list1, list2);

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

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

IEqualityComparer接口

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


这应该是公认的答案。问题不是关于SETS而是关于LISTS,LISTS可能包含重复的元素。
阿德里安·纳苏

3
鉴于的结果SequenceEqual很简单,所以我看不出这将是答案bool。OP需要两个结果列表-并按照设置操作描述它们想要的内容:“显示在第一个列表中而不显示在第二个列表中的项目”。没有迹象表明排序是相关的,而SequenceEqual 确实认为它是相关的。这似乎在回答一个完全不同的问题。
乔恩·斯基特

是的,正确,似乎我回答得太快了,没有看过请求的第二部分……与前两个评论一样……
miguelmpn

9

如果您希望结果不区分大小写,则可以执行以下操作:

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


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

1
这就是您能够比较自定义数据类型的条件。然后使用Except
Pranav Singh

您可以对可排序类型做得更好。这可以在O(n ^ 2)中运行,而您可以执行O(nlogn)。
yuvalm2

3

尝试这种方式:

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

13
这遭受了可怕的性能,要求对第一项中的每个项目都扫描第二项列表。不会因为它起作用而降低投票权,但是它和原始代码一样糟糕。
支出者2012年

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

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

注意:奇怪的名称表示该方法未考虑列表中元素的顺序。如果您确实关心列表中元素的顺序,则此方法不适合您!


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

0

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

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

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


-1

可能很有趣,但对我有用

string.Join(“”,List1)!= string.Join(“”,List2)


就像这里写的那样,它甚至对List <string>或List <int>都不起作用,例如,两个列表11; 2; 3和1; 12; 3完全相同,因为您不需要将字符串与列表中不可能包含的唯一分隔符。除此之外,将包含很多项目的列表的字符串连接起来可能会降低性能。
SwissCoder

@SwissCoder:你错了,这不是字符串的性能杀手。如果您有两个包含50.000个字符串的列表(每个字符串的长度为3),则此算法在我的计算机上需要3毫秒。被接受的应答者需要7。我认为诀窍是Jibz只需要一个字符串比较。当然,他必须添加一个唯一的分隔符。
user1027167

@ user1027167:我不是在谈论直接比较字符串(因为这也不是问题)。调用具有50.000个对象的List中的所有对象的.ToString()方法可以创建一个巨大的字符串,具体取决于实现方式。我不认为这是要走的路。然后,依靠一个字符或字符串是“唯一的”也是有风险的,这样的代码将不会真正可重用。
SwissCoder

好的,是的。发问者要求提供最快的方式而不给出列表的数据类型。这个回答者可能是发问者用例的最快方法。
user1027167

-3

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

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)

3
这是一个C#问题,您尚未提供C#代码。
李慧夏

1
也许您可以删除此答案并将其移至(例如)如何比较python中的两个列表并返回匹配项
李慧夏

-4

这是您会发现的最佳解决方案

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

1
这实际上非常糟糕,因为它会List<T>为中的每个元素创建一个新的list1list3当不是时,也会调用结果List<T>
李慧夏
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.