如何使用Linq对这些结果进行正确分组?
我有以下数据集:如何使用Linq对这些结果进行正确分组?,linq,Linq,我有以下数据集: Year Category Score 2011 A 83 2012 A 86 2013 A 62 2011 B 89 2012 B 86 2013 B 67 2011 C 85 2012 C 73 2013 C 79 2011 D 95 2012 D 7
Year Category Score
2011 A 83
2012 A 86
2013 A 62
2011 B 89
2012 B 86
2013 B 67
2011 C 85
2012 C 73
2013 C 79
2011 D 95
2012 D 78
2013 D 67
我想转换为以下结构
categories: [2011, 2012, 2013],
series: [
{ data: [83, 86, 62], name: 'A' },
{ data: [85, 73, 79], name: 'B' },
{ data: [83, 86, 62], name: 'C' },
{ data: [95, 78, 67], name: 'D' }]
我希望代码能够容忍源数据集中的“缺失”数据。安全的假设是,源数据中至少有一个年份和类别的数据
“粗略”数据示例
Year Category Score
2011 A 83
// 2012 A is missing
2013 A 62
// 2011 B is missing
2012 B 86
2013 B 67
2011 C 85
// 2012 C is missing
2013 C 79
2011 D 95
2012 D 78
2013 D 67
应产生以下结果:
categories: [2011, 2012, 2013],
series: [
{ data: [83, 0, 62], name: 'A' },
{ data: [ 0, 73, 79], name: 'B' },
{ data: [83, 0, 62], name: 'C' },
{ data: [95, 78, 67], name: 'D' }]
从pastebin代码创建了以下LINQPad代码-请参阅实现后的注释:
void Main()
{
var scores = new [] {
new CScore { Year = 2011, Category = 'A', Score = 83 },
// 2012 A is missing
new CScore { Year = 2013, Category = 'A', Score = 62 },
// 2011 B is missing
new CScore { Year = 2012, Category = 'B', Score = 86 },
new CScore { Year = 2013, Category = 'B', Score = 67 },
new CScore { Year = 2011, Category = 'C', Score = 85 },
// 2012 C is missing
new CScore { Year = 2013, Category = 'C', Score = 79 },
new CScore { Year = 2011, Category = 'D', Score = 95 },
new CScore { Year = 2012, Category = 'D', Score = 78 },
new CScore { Year = 2013, Category = 'D', Score = 67 },
};
int[] years = scores.Select(i => i.Year).Distinct()
.OrderBy(i=>i).ToArray();
char[] categories = scores.Select(i => i.Category).Distinct()
.OrderBy(i=>i).ToArray();
var series =
from year in years
from cat in categories
join score in scores
on new { Year = year, Category = cat }
equals new { score.Year, score.Category } into scoreGroup
select scoreGroup.SingleOrDefault() ??
new CScore { Year = year, Category = cat } into scoreWithDefault
group scoreWithDefault.Score by scoreWithDefault.Category into g
select new Series { Name = g.Key.ToString(), Data = g.ToArray() };
years.Dump(); // categories
series.Dump(); // series
}
class CScore
{
public char Category {get;set;}
public int Year {get;set;}
public int Score {get;set;}
}
class Series
{
public string Name {get;set;}
public int[] Data {get;set;}
}
评论
CScore
-重命名以避免遇到命名错误join..into
允许为缺失年份生成默认的CScore
SingleOrDefault
,这样如果输入数据在连接上有多个匹配的CScore项,查询将抛出一个invalidoOperationException
,指示应该做更多的事情来处理冗余。我发现这比在这种坏数据/奇怪数据情况下不会失败的FirstOrDefault
更可取CScore
初始值设定项块中省略了Score=0
,因为0是默认值select..into
querycontinuation允许将查询输入到group..by
中,该组按类别/名称对分数进行分组。我真的很欣赏这里的空合并操作符group..by..into g
--系列
类型类似于I分组
,如果我停止使用group by,我会使用该分组。相反,最终的select项目将该类型分组为所需的系列
类型尽管我们还有更多的话题可以谈,我还是把它留在这里。希望我没有失去任何人。这是我当前的工作代码。我关心的是答案的清晰性和步骤的数量。如果你把你的代码发布到问题中,这是很有用的,大多数人不喜欢跟随第三方链接到别人的东西。如果您的代码很复杂,请将其简化为尽可能简单的方式来演示您的问题。在本例中,一个linq语句演示您所尝试的内容将是理想的。感谢@LukeMcGregor的建议。下次我会把它添加到问题主体中。我不知道是否可以用一个表达式来完成。谢谢@devgeezer!我真的很感谢你的详细解释。我没有意识到我的交叉连接可以用同样的表达式来表达。“进入计分组”可以更清楚地命名,因为结果中没有“组”成分。我也很欣赏SingleOrDefault的改进。之所以选择该scoreGroup名称,是因为编译器将“join..into scoreGroup”转换为一个GroupJoin调用——这可能不是名称的最佳动机。无论如何,我很高兴能为您提供帮助,并感谢您提供了一个有趣的LINQ挑战。