【问题标题】:linq - how do you do a query for items in one query source that are not in another one?linq - 您如何查询一个查询源中不在另一个查询源中的项目?
【发布时间】:2010-09-08 05:16:28
【问题描述】:

如果我有 2 个查询源,我如何找到其中一个查询源而不是另一个查询源?

在两者中查找项目的连接示例:

var results = from item1 in qs1.Items
   join item2 in qs2 on item1.field1 equals item2.field2
   select item1;

那么 linq 代码将返回 qs1 中不在 qs2 中的项目是什么?

【问题讨论】:

    标签: c# .net linq .net-3.5


    【解决方案1】:

    来自Marco Russo

    NorthwindDataContext dc = new NorthwindDataContext();
    dc.Log = Console.Out;
    var query =
        from c in dc.Customers
        where !(from o in dc.Orders
                select o.CustomerID)
               .Contains(c.CustomerID)
        select c;
    foreach (var c in query) Console.WriteLine( c );
    

    【讨论】:

    • 这是否具有 O(N^2) 复杂性,因为它针对每个客户检查所有订单? Darren Kopp 的建议是否更好?
    【解决方案2】:

    使用 except 扩展方法。

    var items1 = new List<string> { "Apple","Orange","Banana" };
    var items2 = new List<string> { "Grapes","Apple","Kiwi" };
    
    var excluded = items1.Except(items2);
    

    【讨论】:

    • 这是更好的方法(见下面我的回答)。
    【解决方案3】:

    达伦·科普的answer

    var excluded = items1.Except(items2);
    

    从性能的角度来看是最好的解决方案。

    (注意:这对于至少常规 LINQ 来说是正确的,也许 LINQ to SQL 会根据 Marco Russo's blog post 改变事情。但是,我想在“最坏的情况”下,Darren Kopp 的方法至少会返回即使在 LINQ to SQL 环境中,Russo 方法的速度也是如此)。

    作为一个简单的例子,在LINQPad 中试试这个:

    void Main()
    {
       Random rand = new Random();
       int n = 100000;
       var randomSeq = Enumerable.Repeat(0, n).Select(i => rand.Next());
       var randomFilter = Enumerable.Repeat(0, n).Select(i => rand.Next());
    
       /* Method 1: Bramha Ghosh's/Marco Russo's method */
       (from el1 in randomSeq where !(from el2 in randomFilter select el2).Contains(el1) select el1).Dump("Result");
    
       /* Method 2: Darren Kopp's method */
       randomSeq.Except(randomFilter).Dump("Result");
    }
    

    尝试一次注释掉这两种方法中的一种,并尝试不同 n 值的性能。

    我的经验(在我的 Core 2 Duo 笔记本电脑上)似乎表明:

    n = 100. Method 1 takes about 0.05 seconds, Method 2 takes about 0.05 seconds
    n = 1,000. Method 1 takes about 0.6 seconds, Method 2 takes about 0.4 seconds
    n = 10,000. Method 1 takes about 2.5 seconds, Method 2 takes about 0.425 seconds
    n = 100,000. Method 1 takes about 20 seconds, Method 2 takes about 0.45 seconds
    n = 1,000,000. Method 1 takes about 3 minutes 25 seconds, Method 2 takes about 1.3 seconds
    

    方法 2(Darren Kopp 的回答)显然更快。

    对于较大的 n,方法 2 的速度下降很可能是由于创建了随机数据(请随意放入 DateTime 差异来确认这一点),而方法 1 显然存在算法复杂性问题(仅通过查看您可以看到它至少是 O(N^2),因为它与整个第二个集合进行比较的第一个集合中的每个数字。

    结论:使用 Darren Kopp 对 LINQ 的 'Except' 方法的回答

    【讨论】:

    • 写得很好。我也喜欢 Enumerable.Repeat() 方法。直到今天才看到那个,但这是一个很好的小方法。
    • 由于这个答案的重要性而改变了接受的答案。它只需要包含答案,而不仅仅是引用它。请编辑此内容以至少包含此内容(当然要给予 Darren 适当的荣誉): var exclude = items1.Except(items2);
    【解决方案4】:

    另一种完全不同的看待它的方式是将 lambda 表达式(用于填充第二个集合的条件)作为谓词传递给第一个集合。

    我知道这不是问题的确切答案。我想其他用户已经给出了正确的答案。

    【讨论】:

      【解决方案5】:

      这里有一个更简单的版本,你不需要嵌套查询:

      List<string> items1 = new List<string>();
      items1.Add("cake");
      items1.Add("cookie");
      items1.Add("pizza");
      
      List<string> items2 = new List<string>();
      items2.Add("pasta");
      items2.Add("pizza");
      
      var results = from item in items1
                where items2.Contains(item)
                select item;
      
      foreach (var item in results)
          Console.WriteLine(item); //Prints 'pizza'
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多