C# 带查询的LINQ;“记忆”;
C# 带查询的LINQ;“记忆”;,c#,.net,linq,C#,.net,Linq,LINQ在查询时是否有办法“记忆”其以前的查询结果 考虑以下情况: public class Foo { public int Id { get; set; } public ICollection<Bar> Bars { get; set; } } public class Bar { public int Id { get; set; } } 在上述情况下,foo1与foo2相似,但foo1和foo2与foo3不相似 假设我们有一个查询结果,该结果由I
LINQ
在查询时是否有办法“记忆”其以前的查询结果
考虑以下情况:
public class Foo {
public int Id { get; set; }
public ICollection<Bar> Bars { get; set; }
}
public class Bar {
public int Id { get; set; }
}
在上述情况下,foo1
与foo2
相似,但foo1
和foo2
与foo3
不相似
假设我们有一个查询
结果,该结果由IEnumerable
或IORDerenumerable
的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;
}
}
如果所需的“内存”来自特定的查询项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;
但是,如果它必须来自之前对查询本身的查询,那么事情就会变得更加麻烦
有办法吗
编辑:
(我目前正在(github链接)寻找答案。仍在思考如何公平地测试所有答案)
(下面的大多数答案都是为了解决我的特定问题,而且都很好(Rob、spender和David B使用IEqualityComparer
的答案特别棒)。不过,如果有人能回答我更一般的问题“LINQ有办法”记忆吗“查询时其先前的查询结果”,我也很高兴)
(除了在使用完全/部分LINQ时,我上面介绍的特定情况在性能上的显著差异外,一个旨在回答我关于LINQ内存的一般问题的答案是Ivan Stoev的。另一个结合良好的答案是Rob的。为了让我自己更清楚,我寻求使用LI的一般有效解决方案(如果有)NQ)想法。你可以通过在缓存上设计自己流畅的突变子接口来破解一些东西,你可以在“let x=…”子句中捕捉到,大致如下:
from q in query
let qc = ... // your cache mechanism here
select ...
但我怀疑您必须小心地将缓存的更新限制为那些“let…”,因为我怀疑,如果您允许通过在“where”或“join”、“groupby”等子句中应用的谓词在它们的后面发生这样的副作用,那么标准Linq操作符和扩展方法的实现是否会令人满意
“嗯,我不打算直接回答你的问题,而是提出一种方法,该方法对于过滤前N个不相似的项目会非常有效
首先,考虑编写<代码> IEqualityComparer <代码>,使用<代码> Bar < /Cuff>集合来度量相等性。在这里,我假设列表可能包含重复的条目,因此有相当严格的相似度定义:
public class FooSimilarityComparer:IEqualityComparer<Foo>
{
public bool Equals(Foo a, Foo b)
{
//called infrequently
return a.Bars.OrderBy(bar => bar.Id).SequenceEqual(b.Bars.OrderBy(bar => bar.Id));
}
public int GetHashCode(Foo foo)
{
//called frequently
unchecked
{
return foo.Bars.Sum(b => b.GetHashCode());
}
}
}
@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);
这假设Id
s对于每个Foo都是唯一的(我想您也可以使用它们的GetHashCode()
)
更好的解决方案是保留已完成的操作,或实现自定义比较器,如下所示:
注意:正如@spender在评论中指出的,下面的等于和GetHashCode
不适用于具有重复项的集合。请参考他们的答案以获得更好的实现-但是,使用代码将保持不变
我猜“完整LINQ”是指标准LINQ运算符/可枚举
扩展方法
我不认为使用LINQ查询语法可以做到这一点。从标准方法来看,唯一支持可变处理状态的方法是,但它给您的只是普通的foreach
:
var result = query.Aggregate(new List<Foo>(), (list, next) =>
{
if (list.Count < 50 && !list.Any(item => areBarsSimilar(item.Bars, next.Bars)))
list.Add(next);
return list;
});
其中自定义方法是
public static class Utils
{
public static List<T> Concat<T>(this List<T> list, T item) { list.Add(item); return list; }
}
公共静态类Utils
{
公共静态列表Concat(此列表,T项){List.Add(项);return List;}
}
但是请注意,与vanillaforeach
相比,Aggregate
还有一个额外的缺点,即不能提前退出,因此将消耗整个输入序列(除了性能之外,还意味着它不能处理无限序列)
结论:虽然这应该回答您最初的问题,即在技术上可以满足您的要求,但LINQ(如标准SQL)并不适合这种类型的处理。IEnumerable dissimilarFoos=
IEnumerable<Foo> dissimilarFoos =
from foo in query
let key = string.Join('|',
from bar in foo.Bars
order by bar.Id
select bar.Id.ToString())
group foo by key into g
select g.First();
IEnumerable<Foo> firstDissimilarFoos =
dissimilarFoos.Take(50);
来自查询中的foo
让key=string.Join(“|”,
来自富国酒吧
酒吧点菜
选择bar.Id.ToString())
按键将foo分组为g
选择g.First();
IEnumerable firstdifferiorfoos=
不同的食物。取(50);
有时,您可能不喜欢上面查询中groupby的行为。在枚举查询时,groupby将枚举整个源。如果您只需要部分枚举,则应切换到Distinct和Comparer:
class FooComparer : IEqualityComparer<Foo>
{
private string keyGen(Foo foo)
{
return string.Join('|',
from bar in foo.Bars
order by bar.Id
select bar.Id.ToString());
}
public bool Equals(Foo left, Foo right)
{
if (left == null || right == null) return false;
return keyGen(left) == keyGen(right);
}
public bool GetHashCode(Foo foo)
{
return keyGen(foo).GetHashCode();
}
}
class FooComparer:IEqualityComparer
{
私有字符串密钥生成(Foo-Foo)
{
返回string.Join(“|”,
来自富国酒吧
酒吧点菜
选择bar.Id.ToString());
}
公共布尔等于(左,右)
{
if(left==null | | right==null)返回false;
返回键根(左)=键根(右);
}
公共bool GetHashCode(Foo-Foo)
{
返回keyGen(foo.GetHashCode();
}
}
然后写:
IEnumerable<Foo> dissimilarFoos = query.Distinct(new FooComparer());
IEnumerable<Foo> firstDissimilarFoos = dissimilarFoos.Take(50);
IEnumerable dissimilarFoos=query.Distinct(new foodcomparer());
IEnumerable firstDissimilarFoos=dissimilarFoos.Take(50);
啊,是的,我想知道qc=…
实际上是什么。:)如果它是由特定查询产生的变量
var result = query.Aggregate(new List<Foo>(), (list, next) =>
{
if (list.Count < 50 && !list.Any(item => areBarsSimilar(item.Bars, next.Bars)))
list.Add(next);
return list;
});
var result = query.Aggregate(new List<Foo>(), (list, next) => list.Count < 50 &&
!list.Any(item => areBarsSimilar(item.Bars, next.Bars)) ? list.Concat(next) : list);
public static class Utils
{
public static List<T> Concat<T>(this List<T> list, T item) { list.Add(item); return list; }
}
IEnumerable<Foo> dissimilarFoos =
from foo in query
let key = string.Join('|',
from bar in foo.Bars
order by bar.Id
select bar.Id.ToString())
group foo by key into g
select g.First();
IEnumerable<Foo> firstDissimilarFoos =
dissimilarFoos.Take(50);
class FooComparer : IEqualityComparer<Foo>
{
private string keyGen(Foo foo)
{
return string.Join('|',
from bar in foo.Bars
order by bar.Id
select bar.Id.ToString());
}
public bool Equals(Foo left, Foo right)
{
if (left == null || right == null) return false;
return keyGen(left) == keyGen(right);
}
public bool GetHashCode(Foo foo)
{
return keyGen(foo).GetHashCode();
}
}
IEnumerable<Foo> dissimilarFoos = query.Distinct(new FooComparer());
IEnumerable<Foo> firstDissimilarFoos = dissimilarFoos.Take(50);