1

我有以下数据集:

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' }]

我希望代码能够容忍源数据集中的“缺失”数据。可以安全地假设,每年和类别中至少有 1 个在源数据中表示。

“粗略”数据示例

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' }]
4

1 回答 1

0

从 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;}
}

评论

  1. CScore- 重命名以避免我遇到的命名错误
  2. 根据输入数据对不同的项目进行排序以避免潜在的排序挑战。
  3. 系列查询:
    1. from 子句形成所有类别/年份组合的交叉产品。
    2. 允许缺失年份的join..into默认CScore生成
    3. 我选择SingleOrDefault这样如果输入数据在连接上有多个匹配的 CScore 项目,查询将抛出一个InvalidOperationException指示应该做更多的事情来处理冗余。我发现这比FirstOrDefault在这种坏数据/奇怪数据情况下不会失败的情况更可取。
    4. Score = 0在初始化程序块中省略,CScore因为 0 是默认值。
    5. select..into查询延续允许将查询输入到group..by按类别/名称对分数进行分组的查询中。我真的很欣赏这里的空合并运算符。
    6. group..by..into g-- 该Series类型类似于IGrouping<char,int>我停止使用 group-by 时使用的类型。而是最终选择 IGrouping 类型为所需Series类型的项目。

我在 LINQPad 输出中验证了答案 - 并在“应该产生这个”示例输出数据中发现了一些缺陷。此外,这段代码在我的机器上执行大约需要一毫秒,所以除非我们有比这更多的数据要处理,否则我不会想改进它。

尽管我们可以谈论的内容更多——我还是把它留在那里。希望我没有失去任何人。

于 2013-02-15T04:36:39.443 回答