LINQ组合查询

4
我有两个不同类型的对象集合,分别称为类型 ALPHA 和类型 BRAVO 。每种类型都有一个属性作为对象的“ID”。在类中没有重复的ID,因此对于任何给定的ID,最多只有一个 ALPHA 和一个 BRAVO 实例。我需要做的是将它们分成三类:
  1. ID出现在 ALPHA 中但未出现在 BRAVO 集合中的实例;
  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
            });

我还可以编写代码来解决第一种和第二种情况,大致如下:

var myUnmatchedAlphas = myAlphaItems.Where(alpha=>!myBravoItems.Any(bravo=>alpha.Id==bravo.Id));

同样地,对于unMatchedBravos也是如此。不幸的是,这将导致对alphas集合(可能非常大!)进行多次迭代,以及对bravos集合(也可能非常大!)进行多次迭代。

有没有办法统一这些查询概念,以最小化对列表的迭代?这些集合可能有数千个项目。

5个回答

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

如果您想要的是Alpha和Bravo本身,

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 方法来自于 MoreLinq 库。
  2. Enumerable.ToDictionary 方法。
  3. 如果两个类型都继承自一个公共类型(例如一个 IHasId 接口),你可以编写自己的 IEqualityComparer<T> 实现;Enumerable.Except 有一个接受相等比较器作为参数的重载方法

1

有时候LINQ并不是最好的解决方案。在这种情况下,我会考虑使用一个带有自定义比较器的HashSet<T>来减少执行集合操作的工作量。相比于列表,哈希集在执行集合操作时更加高效 - 而且(根据数据)可以大大减少工作量:

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

我认为对于 HashSet,你需要一个 IEqualityComparer<ABItem> 而不是一个 IComparer。并且,我会用 a.Id.CompareTo(b.Id) 代替使用 GetId 方法(因为这个通用比较器将接收 ABItems 而不是 objects)。 - Mariano Desanze

1

这里有一个可能的LINQ解决方案,它对两个集合执行完全外连接,并附加一个属性以显示它们属于哪个组。然而,当您尝试将组分开到不同的变量中时,这个解决方案可能会失去其光彩。这真的取决于您需要对这些对象执行什么样的操作。无论如何,在我处理5000个项目的列表时,这个解决方案以(我认为)可接受的速度(0.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;

您可以删除“group by”查询,或从中创建字典(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; }
    } 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接