如何使用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
    -重命名以避免遇到命名错误
  • 根据输入数据对不同的项目进行排序,以避免潜在的排序问题
  • 系列查询:
  • from条款构成所有类别/年份组合的叉积
  • join..into
    允许为缺失年份生成默认的
    CScore
  • 我选择了
    SingleOrDefault
    ,这样如果输入数据在连接上有多个匹配的CScore项,查询将抛出一个
    invalidoOperationException
    ,指示应该做更多的事情来处理冗余。我发现这比在这种坏数据/奇怪数据情况下不会失败的
    FirstOrDefault
    更可取
  • CScore
    初始值设定项块中省略了
    Score=0
    ,因为0是默认值
  • select..into
    querycontinuation允许将查询输入到
    group..by
    中,该组按类别/名称对分数进行分组。我真的很欣赏这里的空合并操作符
  • group..by..into g
    --
    系列
    类型类似于
    I分组
    ,如果我停止使用group by,我会使用该分组。相反,最终的select项目将该类型分组为所需的
    系列
    类型
  • 我在LINQPad输出中验证了答案,并在“应该产生这个”样本输出数据中发现了几个缺陷。而且,这段代码在我的机器上执行大约一毫秒,所以除非我们有比这更多的数据要处理,否则我不会试图改进它


    尽管我们还有更多的话题可以谈,我还是把它留在这里。希望我没有失去任何人。

    这是我当前的工作代码。我关心的是答案的清晰性和步骤的数量。如果你把你的代码发布到问题中,这是很有用的,大多数人不喜欢跟随第三方链接到别人的东西。如果您的代码很复杂,请将其简化为尽可能简单的方式来演示您的问题。在本例中,一个linq语句演示您所尝试的内容将是理想的。感谢@LukeMcGregor的建议。下次我会把它添加到问题主体中。我不知道是否可以用一个表达式来完成。谢谢@devgeezer!我真的很感谢你的详细解释。我没有意识到我的交叉连接可以用同样的表达式来表达。“进入计分组”可以更清楚地命名,因为结果中没有“组”成分。我也很欣赏SingleOrDefault的改进。之所以选择该scoreGroup名称,是因为编译器将“join..into scoreGroup”转换为一个GroupJoin调用——这可能不是名称的最佳动机。无论如何,我很高兴能为您提供帮助,并感谢您提供了一个有趣的LINQ挑战。