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;}
}
但是请注意,与vanilla
foreach
相比,
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);