LINQ Объединить запросы - PullRequest
       27

LINQ Объединить запросы

4 голосов
/ 04 ноября 2010

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

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

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

Ответы [ 5 ]

2 голосов
/ 04 ноября 2010

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

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 из MoreLinq .
  2. Метод Enumerable.ToDictionary.
  3. Если оба типа наследуются от общего типа (например, интерфейса IHasId), вы можете написать собственную реализацию IEqualityComparer<T>; Enumerable.Except имеет перегрузку , которая принимает в качестве параметра средство сравнения равенства.
1 голос
/ 04 ноября 2010
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();
1 голос
/ 04 ноября 2010

Вот одно из возможных решений 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;

Вы можете удалить запрос 'group by' или создать словарь(q.ToDictionary(x => x.Key, x => x.Select(y => y))) или что угодно!Это просто способ категоризации ваших товаров.Я уверен, что есть лучшие решения, но этот вопрос показался мне действительно интересным, поэтому я подумал, что могу попробовать!

1 голос
/ 04 ноября 2010

Иногда 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 голосов
/ 04 ноября 2010

Я думаю, что 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; }
    } 
...