两个列表的差异

190

我有两个填充有CustomsObjects的通用列表。

我需要在第三个列表中检索这两个列表之间的差异(第一个列表中存在但第二个列表中不存在的项目)。

我正在考虑使用.Except()是一个好主意,但我不知道如何使用它。帮帮我!

14个回答

339

使用Except恰好是正确的方法。如果你的类型覆盖了EqualsGetHashCode,或者你只关心引用类型的相等性(即只有两个引用指向完全相同的对象时才“相等”),那么你可以直接使用:

var list3 = list1.Except(list2).ToList();

如果您需要按照自定义标识(例如ID)来表达相等的概念,则需要实现IEqualityComparer<T>接口。例如:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

然后使用:

var list3 = list1.Except(list2, new IdComparer()).ToList();

请注意,这将删除任何重复的元素。如果您需要保留重复项,最简单的方法可能是从list2创建一个集合并使用类似以下的代码:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();

27
顺便提一句,我想补充说明Except是一个集合操作符,因此结果列表中的值将是不同的,例如{'A','A','B','C'}. Except({'B','C'})将返回{'A'} - digEmAll
6
我有这个:{'A','A','B','C'}。Except({'B','C'})返回{'A'},这很有效...然而{'B','C'}。Except({'A','B','C'})并不返回{'A'}。 - MrLister
4
两个集合的差集被定义为第一个集合中不出现在第二个集合中的成员(引用自 MS)。因此,{B,C}.Except({A,B,C}) 应该返回空,因为 B 和 C 都在第二个集合中。这并不是一个错误,而是与函数的数学定义有关。 - Steve Hibbert
那么,@JonSkeet,如果我想基于两个属性比较两个列表,我可以这样编写比较器并获取不相等的项,对吗? - Ehsan Sajjad
1
@NetMage:OP 表示他们想要“在第一个列表中但不在第二个列表中的项目”——这听起来像是一个集合差异。如果第一个列表包含 { 5, 5, 5, 5, 1 },而第二个列表包含 { 5 },那么只有 1 在第一个列表中而不在第二个列表中。 - Jon Skeet
显示剩余2条评论

92

你可以做这样的事情:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));

1
它为我节省了一个foreach循环。 - alice7
12
“l.someproperty” 中的其中一个应该改为 “p.someproperty” 吗? - Manos Dilaverakis
7
可以通过使用customlist.Where(p => otherlist.Any(l => p.someproperty != l.someproperty)) 来简化此操作。 - Dhanuka777
4
@Dhanuka777这是错误的。如果你想这样做,Any应该改成All。因为第二个列表中可能会有来自第一个列表的项目,但如果不是第一个被检查的项目,则它将立即获得p.someproperty != l.someproperty 的 true。这导致返回存在于两个列表中的项。对那6个点赞的人感到遗憾。 - Murphybro2

72

我认为需要强调的是 - 使用Except方法将只返回第一个序列中不包含在第二个序列中的项。它不会返回第二个序列中不在第一个序列中出现的元素。

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

但是,如果你想要获取两个列表之间的真正差异:

第一个列表中有而第二个列表中没有的项目以及第二个列表中有而第一个列表中没有的项目。

你需要使用Except函数两次:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

或者您可以使用 HashSet 的 SymmetricExceptWith 方法。但是它会更改被调用的集合:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7

这很有帮助。只需注意OP明确表示他们不想要完整的差异:“我需要检索这两个列表之间的差异(第一个列表中存在但第二个列表中不存在的项)”。 - rsenna

13
var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

注意:list3将包含两个列表中不同的项目或对象。 注意:正确写法是 ToList() 而不是 toList()


我可以通过避免使用Except方法来使用这些方法。 - Prince

12
var third = first.Except(second);

如果您不喜欢引用延迟加载的集合,还可以在Except()之后调用ToList()

Except()方法将使用默认比较器比较值,如果被比较的值是基本数据类型,如intstringdecimal等。

否则,比较将通过对象地址进行,这可能不是您想要的... 在这种情况下,让您的自定义对象实现IComparable(或实现自定义IEqualityComparer并将其传递给Except()方法)。


7
以下帮助程序在以下情况下可能会有用:
有2个集合,本地集合称为oldValues,远程集合称为newValues。 不时会收到关于远程集合中的一些元素已更改的通知,并且您想知道添加了哪些元素,删除了哪些元素以及更新了哪些元素。 远程集合始终返回其拥有的所有元素。
    public class ChangesTracker<T1, T2>
{
    private readonly IEnumerable<T1> oldValues;
    private readonly IEnumerable<T2> newValues;
    private readonly Func<T1, T2, bool> areEqual;

    public ChangesTracker(IEnumerable<T1> oldValues, IEnumerable<T2> newValues, Func<T1, T2, bool> areEqual)
    {
        this.oldValues = oldValues;
        this.newValues = newValues;
        this.areEqual = areEqual;
    }

    public IEnumerable<T2> AddedItems
    {
        get => newValues.Where(n => oldValues.All(o => !areEqual(o, n)));
    }

    public IEnumerable<T1> RemovedItems
    {
        get => oldValues.Where(n => newValues.All(o => !areEqual(n, o)));
    }

    public IEnumerable<T1> UpdatedItems
    {
        get => oldValues.Where(n => newValues.Any(o => areEqual(n, o)));
    }
}

使用方法

        [Test]
    public void AddRemoveAndUpdate()
    {
        // Arrange
        var listA = ChangesTrackerMockups.GetAList(10); // ids 1-10
        var listB = ChangesTrackerMockups.GetBList(11)  // ids 1-11
            .Where(b => b.Iddd != 7); // Exclude element means it will be delete
        var changesTracker = new ChangesTracker<A, B>(listA, listB, AreEqual);

        // Assert
        Assert.AreEqual(1, changesTracker.AddedItems.Count()); // b.id = 11
        Assert.AreEqual(1, changesTracker.RemovedItems.Count()); // b.id = 7
        Assert.AreEqual(9, changesTracker.UpdatedItems.Count()); // all a.id == b.iddd
    }

    private bool AreEqual(A a, B b)
    {
        if (a == null && b == null)
            return true;
        if (a == null || b == null)
            return false;
        return a.Id == b.Iddd;
    }

4

要从两个列表中获取唯一的差异,您可以将它们合并(Union),但同时排除这两个列表中相同的值(Intersect),例如:

var list1 = new List<int> { 1, 2, 3, 4, 5 };
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var diffs = list1.Union(list2).Except(list1.Intersect(list2));

对于复杂类型,如果您始终使用相同的比较模式来比较实例,则应实现IComparable。如果在某些情况下需要不同的比较模式,则可以始终创建实现IEqualityComparer的类。


1

由于 Except 扩展方法作用于两个 IEumerables,我认为它将是一个 O(n^2) 的操作。如果性能是一个问题(比如说你的列表很大),我建议从 list1 创建一个 HashSet,并使用 HashSet 的 ExceptWith 方法。


9
Enumerable.Except 内部使用 HashSet 或类似的东西。它绝对不使用简单粗暴的 O(n^2) 算法。 - Jim Mischel
@Jim Mischel:你说得对,Enumerable.Except使用了一个内部的Set数据结构,并将两个IEnumerables中的项添加到Set中。但愿文档能够说明一些相关内容。 - foson

1

这是我的解决方案:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}

-1

有点晚了,但这对我来说是可行的解决方案

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

在上述代码中,我正在获取基类和派生类列表之间的属性差异。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接