组内的LINQ到对象索引+用于不同的分组(也称为ROW_NUMBER与PARTITION BY等效)
经过大量的Google搜索和代码实验,我对一个复杂的C#LINQ-to-objects问题感到难过,在SQL中使用一对ROW_NUMBER()… PARTITION BY函数和一个或两个子查询很容易解决这个问题。
用语言来说,这就是我在代码中尝试做的事情 – 基本要求是从列表中删除重复的文档:
- 首先,按(Document.Title,Document.SourceId)对列表进行分组,假设(简化)类定义如下:
class Document { 字符串标题; int SourceId; //来源优先(ID = 1比ID = 2好) }
- 在该组中,为每个文档分配一个索引(例如,索引0 = =第一个文档,此标题来自此来源,索引1 =第二个文档,此标题来自此来源,等等)。 我喜欢SQL中相当于ROW_NUMBER()的东西!
- 现在分组(Document.Title,Index),其中Index在步骤#2中计算。 对于每个组,只返回一个文档:Document.SourceId最低的文档。
步骤#1很简单(例如codepronet.blogspot.com/2009/01/group-by-in-linq.html),但我对步骤#2和#3感到困惑。 我似乎无法构建一个红色无波形的C#LINQ查询来解决所有这三个步骤。
Anders Heilsberg关于这个post的post是我认为如果我能正确理解语法,那么上面的步骤#2和#3的答案。
我倾向于避免使用外部局部变量来执行索引计算,正如slodge.blogspot.com/2009/01/adding-row-number-using-linq-to-objects.html所建议的那样,因为该解决方案中断了如果外部变量被修改。
最佳地,可以首先完成逐个标题步骤,因此“内部”分组(首先由Source计算索引,然后通过索引来过滤掉重复项)可以对每个“按标题”中的少量对象进行操作group,因为每个by-title组中的文档数量通常都在100以下。我真的不想要N 2解决方案!
我当然可以用嵌套的foreach循环解决这个问题,但看起来像LINQ这个问题应该很简单。
有任何想法吗?
我认为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);
首先我们按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 }, };
我得到这个输出:
{ 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 }
更新:我刚看到你关于按标题分组的问题。 您可以使用标题组上的子查询来执行此操作:
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;
说实话,我对你的问题很困惑。 也许你应该解释你想要解决的问题。 无论如何,我会尽力回答我的理解。
1)首先,我假设您已经有一个按Title
+ 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 = 5 }, };
2)要在每个项目中放置索引,可以使用Select
扩展方法,传递Func选择器函数。 像这样:
var docsWithIndex = docs .Select( (d, i) => new { Doc = d, Index = i } );
3)根据我的理解,下一步是按Title
对最后的结果进行分组。 这是怎么做的:
var docsGroupedByTitle = docsWithIndex .GroupBy( a => a.Doc.Title );
GroupBy函数(在上面使用)返回IEnumerable
。 由于一个组也是可枚举的,我们现在有一个可枚举的枚举。
4)现在,对于上面的每个组,我们将只获得具有最小SourceId
的项目。 要进行此操作,我们需要2级递归。 在LINQ中,外层是一个选择(对于每个组,获取其中一个项目),内层是一个聚合(获取具有最低SourceId
的项目):
var selectedFew = docsGroupedByTitle .Select( g => g.Aggregate( (a, b) => (a.Doc.SourceId <= b.Doc.SourceId) ? a : b ) );
为了确保它有效,我用一个简单的foreach
测试它:
foreach (var a in selectedFew) Console.WriteLine(a); //The result will be: //{ Doc = { Title = ABC, SourceId = 0 }, Index = 0 } //{ Doc = { Title = 123, SourceId = 5 }, Index = 4 }
我不确定那是你想要的。 如果没有,请评论答案,我可以解决问题。 我希望这有帮助。
Obs。:我测试中使用的所有类都是匿名的 。 因此,您实际上不需要定义DocumentWithIndex
类型。 实际上,我甚至没有宣布一个Document
类。
基于方法的语法:
var selectedFew = docs.GroupBy(doc => new {doc.Title, doc.SourceId}, doc => doc) .SelectMany((grouping) => grouping.Select((doc, index) => new {doc, index})) .GroupBy(anon => new {anon.doc.Title, anon.index}) .Select(grouping => grouping.Aggregate((a, b) => a.doc.SourceId <= b.doc.SourceId ? a : b));
你会说上面是等效的基于方法的语法吗?
我实现了一个扩展方法。 它支持按字段分区的多个分区以及多个顺序条件。
public static IEnumerable Partition( this IEnumerable source, Func keySelector, Func, IOrderedEnumerable > sorter, Func selector) { AssertUtilities.ArgumentNotNull(source, "source"); return source .GroupBy(keySelector) .Select(arg => sorter(arg).Select(selector)) .SelectMany(arg => arg); }
用法:
var documents = new[] { new { Title = "Title1", SourceId = 1 }, new { Title = "Title1", SourceId = 2 }, new { Title = "Title2", SourceId = 15 }, new { Title = "Title2", SourceId = 14 }, new { Title = "Title3", SourceId = 100 } }; var result = documents .Partition( arg => arg.Title, // partition by arg => arg.OrderBy(x => x.SourceId), // order by (arg, rowNumber) => new { RowNumber = rowNumber, Document = arg }) // select .Where(arg => arg.RowNumber == 0) .Select(arg => arg.Document) .ToList();
结果:
上述就是C#学习教程:组内的LINQ到对象索引+用于不同的分组(也称为ROW_NUMBER与PARTITION BY等效)分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注---计算机技术网(www.ctvol.com)!
{ Title = "Title1", SourceId = 1 }, { Title = "Title2", SourceId = 14 }, { Title = "Title3", SourceId = 100 }
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1023219.html