使用LINQ来获取一个List <>中的项目,而不是另一个List <>中的项目


525

我假设有一个简单的LINQ查询可以做到这一点,但我不确定该如何做。

给出这段代码:

class Program
{
    static void Main(string[] args)
    {
        List<Person> peopleList1 = new List<Person>();
        peopleList1.Add(new Person() { ID = 1 });
        peopleList1.Add(new Person() { ID = 2 });
        peopleList1.Add(new Person() { ID = 3 });

        List<Person> peopleList2 = new List<Person>();
        peopleList2.Add(new Person() { ID = 1 });
        peopleList2.Add(new Person() { ID = 2 });
        peopleList2.Add(new Person() { ID = 3 });
        peopleList2.Add(new Person() { ID = 4 });
        peopleList2.Add(new Person() { ID = 5 });
    }
}

class Person
{
    public int ID { get; set; }
}

我想执行LINQ查询,以使所有peopleList2不在这里的人都进入我的视野peopleList1

这个例子应该给我两个人(ID = 4&ID = 5)


3
最好将ID设置为只读,因为对象的身份不应随其生存时间改变。当然,除非您的测试框架或ORM框架要求它是可变的。
CodesInChaos

2
我们可以根据此图
红豌豆

Answers:


911

可以使用以下LINQ表达式解决此问题:

var result = peopleList2.Where(p => !peopleList1.Any(p2 => p2.ID == p.ID));

通过LINQ表示这种情况的另一种方式,一些开发人员发现这种方式更具可读性:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

警告:如评论中所述,这些方法要求执行O(n * m)操作。可能不错,但可能会带来性能问题,尤其是在数据集很大的情况下。如果这不能满足您的性能要求,则可能需要评估其他选项。由于声明的要求是针对LINQ中的解决方案,因此,这里不探讨这些选项。与往常一样,根据您的项目可能具有的性能要求评估任何方法。


34
您知道这是一个O(n * m)解决方案,可以在O(n + m)时间内轻松解决吗?
Niki 2010年

32
@ nikie,OP要求使用Linq的解决方案。也许他正在尝试学习Linq。如果问题是最有效的方法,那么我的问题不一定是相同的。
克劳斯·拜斯科夫·皮德森

46
@nikie,愿意分享您的简便解决方案吗?
Rubio 2014年

18
这是等效的,我发现更容易理解:var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID!= p.ID));
AntonK

28
@Menol-批评正确回答问题的人可能有点不公平。人们不必预料将来的人们可能会迷失于答案的所有方式和背景。实际上,您应该将其引导给nikie,后者花了点时间说出他们知道替代方法,但没有提供替代方法。
克里斯·罗杰斯

395

如果您覆盖人的平等,那么您还可以使用:

peopleList2.Except(peopleList1)

Except应该比Where(...Any)变体快得多,因为它可以将第二个列表放入哈希表中。Where(...Any)的运行时为,O(peopleList1.Count * peopleList2.Count)而基于HashSet<T>(几乎)的变体的运行时为O(peopleList1.Count + peopleList2.Count)

Except隐式删除重复项。那不应该影响您的情况,但是对于类似情况可能是个问题。

或者,如果您想要快速的代码但又不想覆盖相等性:

var excludedIDs = new HashSet<int>(peopleList1.Select(p => p.ID));
var result = peopleList2.Where(p => !excludedIDs.Contains(p.ID));

此变体不会删除重复项。


仅当Equals被覆盖以比较ID时,这才起作用。
克劳斯·拜斯科夫·皮德森

34
这就是为什么我写道,您需要覆盖平等。但我添加了一个示例,即使没有该示例也可以使用。
CodesInChaos

4
如果Person是一个结构,它也将起作用。实际上,Person似乎是一个不完整的类,因为它具有一个无法识别它的名为“ ID”的属性-如果它确实识别了它,则等于将被覆盖,因此相等的ID意味着等于Person。一旦修复了Person中的错误,这种方法就会更好(除非通过将“ ID”重命名为不会被视为标识符误导的其他错误来修复该错误)。
乔恩·汉娜

2
如果您要谈论的是字符串(或其他基础对象)列表,那么这也非常有用,这是我遇到该线程时要搜索的内容。
丹·科恩

@DanKorn同样,与进行基本比较的int,对象ref,字符串相比,这是一个更简单的解决方案。
迷宫

73

或者,如果您不加任何否定地想要它:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

基本上说是从peopleList2获取全部信息,其中peopleList1中的所有ID与peoplesList2中的ID不同。

与接受的答案略有不同:)


5
此方法(超过50,000个项目的列表)比ANY方法快得多!
DaveN

5
只是因为它很懒,所以可能更快。注意,这还没有做任何实际的工作。直到您枚举列表才可以真正完成工作(通过调用ToList或将其用作foreach循环的一部分,等等)
Xtros

32

由于迄今为止所有解决方案都使用流利的语法,因此对于那些感兴趣的人,这里是查询表达式语法的解决方案:

var peopleDifference = 
  from person2 in peopleList2
  where !(
      from person1 in peopleList1 
      select person1.ID
    ).Contains(person2.ID)
  select person2;

我认为它与给出的答案足以引起某些人的兴趣不同,甚至认为对于Lists来说,它很可能不是最佳选择。现在,对于具有索引ID的表,这绝对是必经之路。


谢谢。第一个答案困扰着查询表达式语法。
通用名称

15

参加聚会有点晚,但是与Linq兼容的一个好的解决方案是:

List<string> list1 = new List<string>() { "1", "2", "3" };
List<string> list2 = new List<string>() { "2", "4" };

List<string> inList1ButNotList2 = (from o in list1
                                   join p in list2 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inList2ButNotList1 = (from o in list2
                                   join p in list1 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inBoth = (from o in list1
                       join p in list2 on o equals p into t
                       from od in t.DefaultIfEmpty()
                       where od != null
                       select od).ToList<string>();

感谢http://www.dotnet-tricks.com/Tutorial/linq/UXPF181012-SQL-Joins-with-C


12

Klaus的回答很好,但是ReSharper会要求您“简化LINQ表达式”:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));


值得注意的是,如果有两个以上的属性绑定两个对象(请考虑SQL复合键),则此技巧将无效。
Alrekr

Alrekr-如果您要说的是“如果需要比较更多的属性,则需要比较更多的属性”,那么我想这很明显。
卢卡斯·摩根

8

此Enumerable Extension可让您定义要排除的项目列表以及用于查找用于执行比较的键的函数。

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> Exclude<TSource, TKey>(this IEnumerable<TSource> source,
    IEnumerable<TSource> exclude, Func<TSource, TKey> keySelector)
    {
       var excludedSet = new HashSet<TKey>(exclude.Select(keySelector));
       return source.Where(item => !excludedSet.Contains(keySelector(item)));
    }
}

你可以这样使用

list1.Exclude(list2, i => i.ID);

通过拥有@BrianT拥有的代码,我如何将其转换为使用您的代码?
Nicke Manarin

0

这是一个工作示例,可以获取应聘者尚不具备的IT技能。

//Get a list of skills from the Skill table
IEnumerable<Skill> skillenum = skillrepository.Skill;
//Get a list of skills the candidate has                   
IEnumerable<CandSkill> candskillenum = candskillrepository.CandSkill
       .Where(p => p.Candidate_ID == Candidate_ID);             
//Using the enum lists with LINQ filter out the skills not in the candidate skill list
IEnumerable<Skill> skillenumresult = skillenum.Where(p => !candskillenum.Any(p2 => p2.Skill_ID == p.Skill_ID));
//Assign the selectable list to a viewBag
ViewBag.SelSkills = new SelectList(skillenumresult, "Skill_ID", "Skill_Name", 1);

0

首先,从条件中的集合中提取ID

List<int> indexes_Yes = this.Contenido.Where(x => x.key == 'TEST').Select(x => x.Id).ToList();

其次,使用“比较”元素来选择与选择内容不同的ID

List<int> indexes_No = this.Contenido.Where(x => !indexes_Yes.Contains(x.Id)).Select(x => x.Id).ToList();

显然,您可以使用x.key!=“ TEST”,但这只是一个示例


0

一旦编写了通用FuncEqualityComparer,就可以在任何地方使用它。

peopleList2.Except(peopleList1, new FuncEqualityComparer<Person>((p, q) => p.ID == q.ID));

public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> comparer;
    private readonly Func<T, int> hash;

    public FuncEqualityComparer(Func<T, T, bool> comparer)
    {
        this.comparer = comparer;
        if (typeof(T).GetMethod(nameof(object.GetHashCode)).DeclaringType == typeof(object))
            hash = (_) => 0;
        else
            hash = t => t.GetHashCode(); 
    }

    public bool Equals(T x, T y) => comparer(x, y);
    public int GetHashCode(T obj) => hash(obj);
}
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.