2010-11-04 4 views
4

У меня есть две коллекции объектов разного типа. Назовите их типа ALPHA и тип BRAVO. Каждый из этих типов имеет свойство, которое является «ID» для объекта. Идентификатор не дублируется внутри класса, поэтому для любого идентификатора не более ALPHA и один BRAVO экземпляр. Что мне нужно сделать, это разделить их на 3 категории:LINQ Combine Queries

  1. экземплярам ID в ALPHA, которые не появляются в BRAVO коллекции;
  2. Экземпляры удостоверения личности в BRAVO которые не отображаются в ALPHA коллекция;
  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. К сожалению, это приведет к многократной итерации коллекции альфов (которые могут быть очень большими!), А также сбор браво (который также может быть очень большим!) Много раз.

Есть ли способ унифицировать эти концепции запросов, чтобы минимизировать итерацию по спискам? Эти коллекции могут иметь тысячи предметов.

ответ

2

Если вы заинтересованы только в идентификаторах,

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

var alphaIdsNotInBravo = alphaIds.Except(bravoIds); 
var bravoIdsNotInAlpha = bravoIds.Except(alphaIds); 

Если вы хотите, альфы и браво себя,

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

EDIT: несколько других вариантов:

  1. ExceptBy method от MoreLinq.
  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 вместо объектов). –

1

Вот один из возможных LINQ решение, которое выполняет полное внешнее соединение на обоих наборах и присоединяет чтобы показать, с какой группой они принадлежат. Это решение может потерять свой блеск, однако, когда вы пытаетесь разделить группы на разные переменные. Все зависит от того, какие действия вам необходимо выполнять на этих объектах. Во всяком случае, это проходило в (я думал) приемлемая скорость (.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 не лучший ответ на эту проблему, если вы хотите пройти и сравнить минимальное количество раз. Я думаю, что следующее итеративное решение более результативно. И я считаю, что читаемость кода не страдает.

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