组内的LINQ到对象索引+用于不同的分组(也称为ROW_NUMBER与PARTITION BY等效)

Jus*_*ant 7 c# linq linq-to-objects group-by row-number

经过大量的Google搜索和代码实验,我对一个复杂的C#LINQ-to-objects问题感到难过,在SQL中使用一对ROW_NUMBER()... PARTITION BY函数和一个或两个子查询很容易解决这个问题.

用语言来说,这就是我在代码中尝试做的事情 - 基本要求是从列表中删除重复的文档:

  1. 首先,按(Document.Title,Document.SourceId)对列表进行分组,假设(简化)类定义如下:
    class Document
    {
        string Title;
        int SourceId; // sources are prioritized (ID=1 better than ID=2)
    }
  2. 在该组中,为每个文档分配一个索引(例如,索引0 = =第一个文档,此标题来自此源,索引1 =第二个文档,此标题来自此源,等等).我喜欢SQL中相当于ROW_NUMBER()的东西!

  3. 现在分组(Document.Title,Index),其中Index在步骤#2中计算.对于每个组,只返回一个文档:Document.SourceId最低的文档.

步骤#1很简单(例如codepronet.blogspot.com/2009/01/group-by-in-linq.html),但我对步骤#2和#3感到困惑.我似乎无法构建一个无红色波形的C#LINQ查询来解决所有这三个步骤.

Anders Heilsberg关于这个帖子的帖子是我认为如果我能正确理解语法,那么上面的步骤#2和#3的答案.

我倾向于避免使用外部局部变量来执行索引计算,正如slodge.blogspot.com/2009/01/adding-row-number-using-linq-to-objects.html所建议的那样,因为该解决方案中断了如果外部变量被修改.

最佳地,可以首先完成逐个标题步骤,因此"内部"分组(首先由Source计算索引,然后通过索引来过滤掉重复项)可以对每个"按标题"的少量对象进行操作group,因为每个by-title组中的文档数量通常在100以下.我真的不想要N 2解决方案!

我当然可以用嵌套的foreach循环解决这个问题,但看起来像LINQ这个问题应该很简单.

有任何想法吗?

dah*_*byk 6

我认为jpbochi错过了你希望你的分组是成对的值(Title + SourceId然后是Title + Index).这是一个LINQ查询(主要)解决方案:

var selectedFew = 
    from doc in docs
    group doc by new { doc.Title, doc.SourceId } into g
    from docIndex in g.Select((d, i) => new { Doc = d, Index = i })
    group docIndex by new { docIndex.Doc.Title, docIndex.Index } into g
    select g.Aggregate((a,b) => (a.Doc.SourceId <= b.Doc.SourceId) ? a : b);
Run Code Online (Sandbox Code Playgroud)

首先我们按Title + SourceId进行分组(我使用匿名类型,因为编译器为分组查找构建了一个好的哈希码).然后我们使用Select将分组的索引附加到文档中,我们在第二个分组中使用它.最后,对于每个组,我们选择最低的SourceId.

鉴于此输入:

var docs = new[] {
    new { Title = "ABC", SourceId = 0 },
    new { Title = "ABC", SourceId = 4 },
    new { Title = "ABC", SourceId = 2 },
    new { Title = "123", SourceId = 7 },
    new { Title = "123", SourceId = 7 },
    new { Title = "123", SourceId = 7 },
    new { Title = "123", SourceId = 5 },
    new { Title = "123", SourceId = 5 },
};
Run Code Online (Sandbox Code Playgroud)

我得到这个输出:

{ Doc = { Title = ABC, SourceId = 0 }, Index = 0 }
{ Doc = { Title = 123, SourceId = 5 }, Index = 0 }
{ Doc = { Title = 123, SourceId = 5 }, Index = 1 }
{ Doc = { Title = 123, SourceId = 7 }, Index = 2 }
Run Code Online (Sandbox Code Playgroud)

更新:我刚看到你关于按标题分组的问题.您可以使用标题组上的子查询执行此操作:

var selectedFew =
    from doc in docs
    group doc by doc.Title into titleGroup
    from docWithIndex in
        (
            from doc in titleGroup
            group doc by doc.SourceId into idGroup
            from docIndex in idGroup.Select((d, i) => new { Doc = d, Index = i })
            group docIndex by docIndex.Index into indexGroup
            select indexGroup.Aggregate((a,b) => (a.Doc.SourceId <= b.Doc.SourceId) ? a : b)
        )
    select docWithIndex;
Run Code Online (Sandbox Code Playgroud)