2010-11-04 98 views
4

我有两个不同类型的对象集合。让我们称他们为ALPHABRAVO。每种类型都有一个属性,即该对象的“ID”。没有ID在类内复制,因此对于任何给定的ID,最多只有一个实例是ALPHA和一个实例。我需要做的就是把它们分为3类:ALPHA不出现BRAVO集合中的ID的LINQ组合查询

  1. 实例;
  2. ID中的实例BRAVO哪些没有出现在ALPHA集合中;
  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(也可能非常大!)很多次。

有什么办法统一这些查询概念,以便最大限度地减少对列表的迭代?这些集合可以有数千个项目。

回答

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

如果你想阿尔法和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.Excepthas an overload接受相等比较器作为参数。
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 
+0

我觉得对于一个HashSet你需要一个'的IEqualityComparer '而不是'IComparer'。而不是使用GetId方法,我只会执行'a.Id.CompareTo(b.Id)'(因为使用这个通用比较器,您将接收ABItems而不是对象)。 – 2010-11-04 17:49:24

1

这里是执行一个完全外部的一个可能的LINQ解决方案参加在两套,并附加财产给他们显示他们属于哪个组。但是,当您尝试将组分成不同的变量时,此解决方案可能会失去光泽。这一切都取决于你需要对这些对象执行什么样的动作。在这个跑在(我认为)可接受的速度(0.5秒)我对5000项的列表任何率:

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)) ), 管他呢!这只是一种分类项目的方法。我确信有更好的解决方案,但这似乎是一个非常有趣的问题,所以我认为我不妨试试它!

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

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

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

定义:

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