考虑以下情况:
public class Foo { public int Id { get; set; } public ICollection<Bar> Bars { get; set; } } public class Bar { public int Id { get; set; } }
现在,如果两个或更多的Foo有相同的Bar收集(不管是什么顺序),它们被认为是类似的Foo.
例:
foo1.Bars = new List<Bar>() { bar1,bar2 }; foo2.Bars = new List<Bar>() { bar2,bar1 }; foo3.Bars = new List<Bar>() { bar3,bar1,bar2 };
在上述情况下,foo1类似于foo2,但foo1和foo2与foo2不相似
假设我们有一个包含IEnumerable或IOrderedEnumerable的Foo的查询结果.从查询中,我们要找到不相似的第一个N foo.
这个任务似乎需要在之前选择的条的集合的记忆.
使用部分LINQ,我们可以这样做:
private bool areBaRSSimilar(ICollection<Bar> bars1,ICollection<Bar> bars2) { return bars1.Count == bars2.Count && //have the same amount of bars !bars1.Select(x => x.Id) .Except(bars2.Select(y => y.Id)) .Any(); //and when excepted does not return any element mean similar bar } public void somewhereWithQueryResult(){ . . List<Foo> topNFoos = new List<Foo>(); //this serves as a memory for the prevIoUs query int N = 50; //can be any number foreach (var q in query) { //query is IOrderedEnumerable or IEnumerable if (topNFoos.Count == 0 || !topNFoos.Any(foo => areBaRSSimilar(foo.Bars,q.Bars))) topNFoos.Add(q); if (topNFoos.Count >= N) //We have had enough Foo break; } }
topNFoos列表将作为上一个查询的记忆,我们可以跳过foreach循环中的Foo q,该循环已经具有与顶部NFo中的任何Foo相同的条.
我的问题是在LINQ(完全LINQ)有什么办法吗?
var topNFoos = from q in query //put something select q;
如果所需的“内存”来自特定查询项目q或查询外部的变量,那么我们可以使用let变量来缓存它:
int index = 0; var topNFoos = from q in query let qc = index++ + q.Id //depends on q or variable outside like index,then it is OK select q;
但是如果它必须来自查询本身的先前查询,那么事情开始变得更麻烦了.
有什么办法吗?
编辑:
(我目前是creating a test case(github链接)的答案,仍然在弄清楚如何公正地测试所有的答案)
(下面的大部分答案都是为了解决我的特定问题,本身就很好(Rob’s,Spender和David B的使用IEqualityComparer的答案尤其棒极了)然而,如果有人能够回答我更普遍的问题“LINQ有一种方法可以”记住“其查询结果,同时查询”,我也很高兴)
(除了上面使用完整/部分LINQ的上述特殊情况下的性能方面的显着差异,一个旨在回答我关于LINQ内存的一般问题的答案是Ivan Stoev,另一个与Rob的组合很好,为了让自己更清楚,我寻找一般高效的解决方案,如果有的话,使用LINQ)
解决方法
var res = query.Select(q => new { original = q,matches = query.Where(innerQ => areBaRSSimilar(q.Bars,innerQ.Bars)) }).Select(g => new { original = g,joinKey = string.Join(",",g.matches.Select(m => m.Id)) }) .GroupBy (g => g.joinKey) .Select(g => g.First().original.original) .Take(N);
这假设Ids对于每个Foo是唯一的(我也可以使用他们的GetHashCode()).
一个更好的解决方案是保持您所做的工作,或实现自定义比较器,如下所示:
注意:正如@spender的评论所指出的,下面的Equals和GetHashCode将不适用于具有重复项的集合.参考他们的答案更好的实现 – 但是,使用代码将保持不变
class MyComparer : IEqualityComparer<Foo> { public bool Equals(Foo left,Foo right) { return left.Bars.Count() == right.Bars.Count() && //have the same amount of bars left.Bars.Select(x => x.Id) .Except(right.Bars.Select(y => y.Id)) .ToList().Count == 0; //and when excepted returns 0,mean similar bar } public int GetHashCode(Foo foo) { unchecked { int hc = 0; if (foo.Bars != null) foreach (var p in foo.Bars) hc ^= p.GetHashCode(); return hc; } } }
然后您的查询变得简单:
var res = query .GroupBy (q => q,new MyComparer()) .Select(g => g.First()) .Take(N);