LINQ组合查询

时间:2010-11-04 15:19:21

标签: c# .net linq query-optimization

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

  1. ALPHA 中ID的实例,该实例未出现在 BRAVO 集合中;
  2. BRAVO 中ID的实例,该实例未出现在 ALPHA 集合中;
  3. 两个集合中出现的ID实例。
  4. 在所有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。不幸的是,这会导致多次迭代alphas(可能非常大!)的集合,以及bravos(也可能非常大!)的集合多次。

    有没有办法统一这些查询概念,以尽量减少列表的迭代?这些集合可以有数千个项目。

5 个答案:

答案 0 :(得分:2)

如果您只对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);

如果你想要alphas和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 methodMoreLinq
  2. Enumerable.ToDictionary方法。
  3. 如果两种类型都继承自公共类型(例如IHasId接口),则可以编写自己的IEqualityComparer<T>实现; Enumerable.Except has an overload接受相等比较器作为参数。

答案 1 :(得分:1)

有时LINQ不是答案。这是我考虑将HashSet<T>与自定义比较器一起使用来减少执行集合操作的工作的问题。 HashSets在执行集合操作方面比列表更有效 - 而且(取决于数据)可以大大减少工作量:

// 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

答案 2 :(得分:1)

这是一个可能的LINQ解决方案,它在两个集合上执行完全外部联接,并向它们附加一个属性,显示它们属于哪个组。但是,当您尝试将组分成不同的变量时,此解决方案可能会失去光彩。这一切都取决于您需要对这些对象执行何种操作。无论如何,这个(我认为)在5000件物品的清单上以可接受的速度(.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 => x.Key, x => x.Select(y => y)))或其他任何内容创建字典!这只是一种对商品进行分类的方法。我确信那里有更好的解决方案,但这似乎是一个非常有趣的问题,所以我想我也可以试一试!

答案 3 :(得分:1)

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

答案 4 :(得分:0)

如果您想遍历并比较最少的次数,我认为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; }
    }