2010-11-04 100 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; } 
    }