【问题标题】:LINQ Combine QueriesLINQ 组合查询
【发布时间】:2011-05-05 03:11:45
【问题描述】:

我有两个不同类型的对象集合。我们称它们为 ALPHABRAVO。这些类型中的每一种都有一个属性,即对象的“ID”。类中没有重复的 ID,因此对于任何给定的 ID,最多有一个 ALPHA 和一个 BRAVO 实例。我需要做的是将它们分为 3 类:

  1. ALPHA 中未出现在 BRAVO 集合中的 ID 实例;
  2. BRAVO 中未出现在 ALPHA 集合中的 ID 实例;
  3. 出现在两个集合中的 ID 实例。

在所有 3 种情况下,我都需要将集合中的实际对象放在手边以进行后续操作。

我知道对于第 3 种情况,我可以这样做:

 var myCorrelatedItems = myAlphaItems.Join(myBravoItems, alpha => alpha.Id, beta => beta.Id, (inner, outer) => new
            {
                alpha = inner,
                beta = outer
            });

我还可以为 #1 和 #2 的情况编写代码,看起来像

var myUnmatchedAlphas = myAlphaItems.Where(alpha=>!myBravoItems.Any(bravo=>alpha.Id==bravo.Id));

对于 unMatchedBravos 也是如此。不幸的是,这将导致多次迭代 alpha 集合(可能非常大!),以及多次迭代 bravos 集合(也可能非常大!)。

有什么方法可以统一这些查询概念,以尽量减少对列表的迭代?这些集合可以包含数千个项目。

【问题讨论】:

    标签: c# .net linq query-optimization


    【解决方案1】:

    如果您只对 ID 感兴趣,

    var alphaIds = myAlphaItems.Select(alpha => alpha.ID);
    var bravoIds = myBravoItems.Select(bravo => bravo.ID);
    
    var alphaIdsNotInBravo = alphaIds.Except(bravoIds);
    var bravoIdsNotInAlpha = bravoIds.Except(alphaIds);
    

    如果您想要 alpha 和 bravos 本身,

    var alphaIdsSet = new HashSet<int>(alphaIds);
    var bravoIdsSet = new HashSet<int>(bravoIds);
    
    var alphasNotInBravo = myAlphaItems
                           .Where(alpha => !bravoIdsSet.Contains(alpha.ID));
    
    var bravosNotInAlpha = myBravoItems
                           .Where(bravo => !alphaIdsSet.Contains(bravo.ID));
    

    编辑: 其他一些选择:

    1. ExceptBy method 来自MoreLinq
    2. Enumerable.ToDictionary 方法。
    3. 如果两种类型都继承自一个通用类型(例如IHasId 接口),您可以编写自己的IEqualityComparer&lt;T&gt; 实现; Enumerable.Except has an overload 接受一个相等比较器作为参数。

    【讨论】:

      【解决方案2】:

      有时 LINQ 不是答案。这是我会考虑使用带有自定义比较器的 HashSet&lt;T&gt; 来减少执行集合操作的工作的问题。 HashSet 在执行集合操作方面比列表更有效 - 并且(取决于数据)可以大大减少工作:

      // create a wrapper class that can accomodate either an Alpha or a Bravo
      class ABItem { 
         public Object Instance   { get; private set; }
         public int Id            { get; private set; }
         public ABItem( Alpha a ) { Instance = a; Id = a.Id; }
         public ABItem( Bravo b ) { Instance = b; Id = b.Id; }
      }
      
      // comparer that compares Alphas and Bravos by id
      class ABItemComparer : IComparer {
         public int Compare( object a, object b ) { 
             return GetId(a).Compare(GetId(b));
         }
      
         private int GetId( object x ) {
             if( x is Alpha ) return ((Alpha)x).Id;
             if( x is Bravo ) return ((Bravo)x).Id;
             throw new InvalidArgumentException();
         }
      }
      
      // create a comparer based on comparing the ID's of ABItems
      var comparer = new ABComparer(); 
      
      var hashAlphas = 
          new HashSet<ABItem>(myAlphaItems.Select(x => new ABItem(x)),comparer);
      
      var hashBravos = 
          new HashSet<ABItem>(myBravoItems.Select(x => new ABItem(x)),comparer);
      
      // items with common IDs in Alpha and Bravo sets:
      var hashCommon = new HashSet<Alpha>(hashAlphas).IntersectWith( hashSetBravo );
      
      hashSetAlpha.ExceptWith( hashSetCommon );  // items only in Alpha
      hashSetBravo.ExceptWith( hashSetCommon );  // items only in Bravo
      

      【讨论】:

      • 我认为对于 HashSet,您需要 IEqualityComparer&lt;ABItem&gt; 而不是 IComparer。而不是使用 GetId 方法,我只会做a.Id.CompareTo(b.Id)(因为使用这个通用比较器,您将收到 ABItems 而不是对象)。
      【解决方案3】:

      这是一种可能的 LINQ 解决方案,它在两个集合上执行完全外连接,并向它们附加一个属性,显示它们属于哪个组。但是,当您尝试将组分成不同的变量时,此解决方案可能会失去其光彩。这完全取决于您需要对这些对象执行什么样的操作。无论如何,这在 5000 个项目的列表中以(我认为)可接受的速度(0.5 秒)运行:

      var q =
        from g in
        (from id in myAlphaItems.Select(a => a.ID).Union(myBravoItems.Select(b => b.ID))
        join a in myAlphaItems on id equals a.ID into ja
        from a in ja.DefaultIfEmpty()
        join b in myBravoItems on id equals b.ID into jb
        from b in jb.DefaultIfEmpty()
        select  (a == null ? 
                  new { ID = b.ID, Group = "Bravo Only" } : 
                  (b == null ? 
                      new { ID = a.ID, Group = "Alpha Only" } : 
                      new { ID = a.ID, Group = "Both" }
                  )
              )
          )
        group g.ID by g.Group;
      

      您可以删除“分组依据”查询或从中创建字典 (q.ToDictionary(x =&gt; x.Key, x =&gt; x.Select(y =&gt; y))),或其他任何方式!这只是对您的项目进行分类的一种方式。我确信那里有更好的解决方案,但这似乎是一个非常有趣的问题,所以我想我不妨试一试!

      【讨论】:

        【解决方案4】:
        Dictionary<int, Alpha> alphaDictionary = myAlphaItems.ToDictionary(a => a.Id);
        Dictionary<int, Bravo> bravoDictionary = myBravoItems.ToDictionary(b => b.Id);
        
        ILookup<string, int> keyLookup = alphaDictionary.Keys
          .Union(bravoDictionary.Keys)
          .ToLookup(x => alphaDictionary.ContainsKey(x) ?
            (bravoDictionary.ContainsKey(x) ? "both" : "alpha") :
            "bravo");
        
        List<Alpha> alphaBoth = keyLookup["both"].Select(x => alphaDictionary[x]).ToList();
        List<Bravo> bravoBoth = keyLookup["both"].Select(x => bravoDictionary[x]).ToList();
        
        List<Alpha> alphaOnly = keyLookup["alpha"].Select(x => alphaDictionary[x]).ToList();
        List<Bravo> bravoOnly = keyLookup["bravo"].Select(x => bravoDictionary[x]).ToList();
        

        【讨论】:

          【解决方案5】:

          如果你想遍历和比较最少的次数,我认为 LINQ 不是这个问题的最佳答案。我认为以下迭代解决方案性能更高。而且我相信代码的可读性不会受到影响。

          var dictUnmatchedAlphas = myAlphaItems.ToDictionary(a => a.Id);
          var myCorrelatedItems = new List<AlphaAndBravo>();
          var myUnmatchedBravos = new List<Bravo>();
          foreach (Bravo b in myBravoItems)
          {
              var id = b.Id;
              if (dictUnmatchedAlphas.ContainsKey(id))
              {
                  var a = dictUnmatchedAlphas[id];
                  dictUnmatchedAlphas.Remove(id); //to get just the unmatched alphas
                  myCorrelatedItems.Add(new AlphaAndBravo { a = a, b = b});
              }
              else
              {
                  myUnmatchedBravos.Add(b);
              }
          }
          

          AlphaAndBravo 的定义:

              public class AlphaAndBravo {
                  public Alpha a { get; set; }
                  public Bravo b { get; set; }
              } 
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-01-11
            • 1970-01-01
            • 1970-01-01
            • 2016-05-24
            • 1970-01-01
            • 1970-01-01
            • 2013-08-11
            相关资源
            最近更新 更多