C# 多列数据转换

C# 多列数据转换,c#,.net,linq,c#-4.0,pivot,C#,.net,Linq,C# 4.0,Pivot,我从一个数据源接收数据,在将信息发送到UI进行显示之前,我需要对该数据源进行透视我对旋转的概念不太熟悉&我不知道该怎么做。 该问题分为两部分: 形成头部 旋转数据以匹配标题 要记住的事情: class Program { static void Main(string[] args) { var _staticColumnCount = 2; //Columns that should not be pivoted

我从一个数据源接收数据,在将信息发送到UI进行显示之前,我需要对该数据源进行透视<代码>我对旋转的概念不太熟悉&我不知道该怎么做。

该问题分为两部分:

  • 形成头部
  • 旋转数据以匹配标题
  • 要记住的事情:

    class Program
        {
            static void Main(string[] args)
            {
                var _staticColumnCount = 2; //Columns that should not be pivoted        
                var _dynamicColumnCount = 2; // Columns which needs to be pivoted to form header            
                var _valueColumnCount = 1; //Columns that represent Actual value        
                var valueColumnIndex = 4; //Assuming index starts with 0;
    
                List<List<string>> headerInfo = new List<List<string>>();
                headerInfo.Add(new List<string> {"Product Three", "Item Ten"});
                headerInfo.Add(new List<string> {"Product Two", "Item Five"});
                headerInfo.Add(new List<string> {"Product Two", "Item Seven"});
                headerInfo.Add(new List<string> {"Product Two", "Item Nine"});
                headerInfo.Add(new List<string> {"Product One", "Item One"});
                headerInfo.Add(new List<string> {"Product One", "Item Two"});
                headerInfo.Add(new List<string> {"Product One", "Item Four"});
                headerInfo.Add(new List<string> {"Product One", "Item Six"});
                headerInfo.Add(new List<string> {"Product One", "Item Eight"});
                headerInfo.Add(new List<string> {"Product One", "Item Eleven"});
    
    
                List<List<string>> data = new List<List<string>>();
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item One", "579984.59"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Two", "314586.73"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item One", "62735.13"});
                data.Add(new List<string> {"Global", "Asia", "Product Two", "Item Five", "12619234.69"});
                data.Add(new List<string> {"Global", "North America", "Product Two", "Item Five", "8953713.39"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Two", "124267.4"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item Four", "482338.49"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Four", "809185.13"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Four", "233101"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item Two", "120561.65"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Six", "1517359.37"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Six", "382590.45"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Eight", "661835.64"});
                data.Add(new List<string> {"Global", "Europe", "Product Three", "Item Three", "0"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Eight", "0"});
                data.Add(new List<string> {"Global", "Europe", "Product Two", "Item Five", "3478145.38"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item Six", "0"});
                data.Add(new List<string> {"Global", "North America", "Product Two", "Item Seven", "4247059.97"});
                data.Add(new List<string> {"Global", "Asia", "Product Two", "Item Seven", "2163718.01"});
                data.Add(new List<string> {"Global", "Europe", "Product Two", "Item Seven", "2158782.48"});
                data.Add(new List<string> {"Global", "North America", "Product Two", "Item Nine", "72634.46"});
                data.Add(new List<string> {"Global", "Europe", "Product Two", "Item Nine", "127500"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item One", "110964.44"});
                data.Add(new List<string> {"Global", "Asia", "Product Three", "Item Ten", "2064.99"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Eleven", "0"});
                data.Add(new List<string> {"Global", "Asia", "Product Two", "Item Nine", "1250"});
    
    
            }
        }
    
  • 我有一些我不想旋转的列。我称它们为
    静态列。

  • 我需要旋转某些列以形成多级标题信息。我称它们为
    动态列

  • 某些列需要旋转,其中包含实际值。我称它们为
    值列

  • 动态、静态和值列的数量没有限制

  • 我们假设,当数据出现时,首先会有静态列的数据,然后是动态列的数据,然后是值列的数据

    有关详细信息,请参阅所附图像。

  • 虚拟数据:

    class Program
        {
            static void Main(string[] args)
            {
                var _staticColumnCount = 2; //Columns that should not be pivoted        
                var _dynamicColumnCount = 2; // Columns which needs to be pivoted to form header            
                var _valueColumnCount = 1; //Columns that represent Actual value        
                var valueColumnIndex = 4; //Assuming index starts with 0;
    
                List<List<string>> headerInfo = new List<List<string>>();
                headerInfo.Add(new List<string> {"Product Three", "Item Ten"});
                headerInfo.Add(new List<string> {"Product Two", "Item Five"});
                headerInfo.Add(new List<string> {"Product Two", "Item Seven"});
                headerInfo.Add(new List<string> {"Product Two", "Item Nine"});
                headerInfo.Add(new List<string> {"Product One", "Item One"});
                headerInfo.Add(new List<string> {"Product One", "Item Two"});
                headerInfo.Add(new List<string> {"Product One", "Item Four"});
                headerInfo.Add(new List<string> {"Product One", "Item Six"});
                headerInfo.Add(new List<string> {"Product One", "Item Eight"});
                headerInfo.Add(new List<string> {"Product One", "Item Eleven"});
    
    
                List<List<string>> data = new List<List<string>>();
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item One", "579984.59"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Two", "314586.73"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item One", "62735.13"});
                data.Add(new List<string> {"Global", "Asia", "Product Two", "Item Five", "12619234.69"});
                data.Add(new List<string> {"Global", "North America", "Product Two", "Item Five", "8953713.39"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Two", "124267.4"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item Four", "482338.49"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Four", "809185.13"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Four", "233101"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item Two", "120561.65"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Six", "1517359.37"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Six", "382590.45"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item Eight", "661835.64"});
                data.Add(new List<string> {"Global", "Europe", "Product Three", "Item Three", "0"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Eight", "0"});
                data.Add(new List<string> {"Global", "Europe", "Product Two", "Item Five", "3478145.38"});
                data.Add(new List<string> {"Global", "Asia", "Product One", "Item Six", "0"});
                data.Add(new List<string> {"Global", "North America", "Product Two", "Item Seven", "4247059.97"});
                data.Add(new List<string> {"Global", "Asia", "Product Two", "Item Seven", "2163718.01"});
                data.Add(new List<string> {"Global", "Europe", "Product Two", "Item Seven", "2158782.48"});
                data.Add(new List<string> {"Global", "North America", "Product Two", "Item Nine", "72634.46"});
                data.Add(new List<string> {"Global", "Europe", "Product Two", "Item Nine", "127500"});
                data.Add(new List<string> {"Global", "North America", "Product One", "Item One", "110964.44"});
                data.Add(new List<string> {"Global", "Asia", "Product Three", "Item Ten", "2064.99"});
                data.Add(new List<string> {"Global", "Europe", "Product One", "Item Eleven", "0"});
                data.Add(new List<string> {"Global", "Asia", "Product Two", "Item Nine", "1250"});
    
    
            }
        }
    
    类程序
    {
    静态void Main(字符串[]参数)
    {
    var _staticColumnCount=2;//不应旋转的列
    var _dynamicColumnCount=2;//需要旋转以形成标题的列
    var _valueColumnCount=1;//表示实际值的列
    var valueColumnIndex=4;//假设索引以0开头;
    列表标题信息=新列表();
    添加(新列表{“产品三”,“项目十”});
    添加(新列表{“产品二”,“项目五”});
    添加(新列表{“产品二”,“项目七”});
    添加(新列表{“产品二”,“项目九”});
    添加(新列表{“产品一”,“项目一”});
    添加(新列表{“产品一”,“项目二”});
    添加(新列表{“产品一”,“项目四”});
    添加(新列表{“产品一”,“项目六”});
    添加(新列表{“产品一”,“项目八”});
    添加(新列表{“产品一”,“项目十一”});
    列表数据=新列表();
    新增(新名单{“全球”、“欧洲”、“产品一”、“项目一”、“579984.59”);
    增加(新的清单{“全球”、“北美”、“产品一”、“项目二”、“314586.73”);
    增加(新的清单{“全球”、“亚洲”、“产品一”、“项目一”、“62735.13”);
    增加(新的清单{“全球”、“亚洲”、“产品二”、“项目五”、“12619234.69”);
    增加(新的清单{“全球”、“北美”、“产品二”、“项目五”、“8953713.39”);
    增加(新的清单{“全球”、“欧洲”、“产品一”、“项目二”、“124267.4”});
    增加(新的清单{“全球”、“亚洲”、“产品一”、“项目四”、“482338.49”);
    增加(新的清单{“全球”、“北美”、“产品一”、“项目四”、“809185.13”);
    新增(新名单{“全球”、“欧洲”、“产品一”、“项目四”、“233101”});
    增加(新的清单{“全球”、“亚洲”、“产品一”、“项目二”、“120561.65”});
    新增(新名单{“全球”、“北美”、“产品一”、“项目六”、“1517359.37”);
    增加(新的清单{“全球”、“欧洲”、“产品一”、“项目六”、“382590.45”);
    新增(新名单{“全球”、“北美”、“产品一”、“项目八”、“661835.64”});
    添加(新列表{“全球”、“欧洲”、“产品三”、“项目三”、“0”});
    添加(新清单{“全球”、“欧洲”、“产品一”、“项目八”、“0”});
    增加(新的清单{“全球”、“欧洲”、“产品二”、“项目五”、“3478145.38”);
    添加(新列表{“全球”、“亚洲”、“产品一”、“项目六”、“0”});
    增加(新的清单{“全球”、“北美”、“产品二”、“项目七”、“4247059.97”);
    增加(新的清单{“全球”、“亚洲”、“产品二”、“项目七”、“2163718.01”);
    增加(新的清单{“全球”、“欧洲”、“产品二”、“项目七”、“2158782.48”);
    增加(新的清单{“全球”、“北美”、“产品二”、“项目九”、“72634.46”);
    新增(新名单{“全球”、“欧洲”、“产品二”、“项目九”、“127500”);
    添加(新的清单{“全球”、“北美”、“产品一”、“项目一”、“110964.44”});
    增加(新的清单{“全球”、“亚洲”、“产品三”、“项目十”、“2064.99”});
    添加(新清单{“全球”、“欧洲”、“产品一”、“项目十一”、“0”});
    增加(新的清单{“全球”、“亚洲”、“产品二”、“项目九”、“1250”);
    }
    }
    
    您可以使用库以以下方式按任意列数创建透视表(不要忘记安装“NReco.PivotData”nuget软件包):

    //数据集中的行表示为“数组”
    //让我们定义“字段名”->“字段索引”映射
    var fieldToIndex=新字典(){
    {“区域L1”,0},
    {“区域L2”,1},
    {“产品L1”,2},
    {“产品L2”,3},
    {“Val”,4}
    };
    //创建多维数据集
    var pvtData=新的数据透视(
    //按4个维度分组
    新[]{“区域L1”、“区域L2”、“产品L1”、“产品L2”},
    //值(对多个值使用CompositeAggerFactory)
    新苏门答腊工厂(“Val”);
    ProcessData(数据,(行,字段)=>((IList)行)[fieldToIndex[field]]);
    //通过分组数据创建透视表数据模型
    var pvtTbl=新数据透视表(
    //行的尺寸
    新[]{“区域L1”,“区域L2”},
    //柱的尺寸
    新[]{“产品L1”,“产品L2”},
    pvtData);
    //现在您可以遍历“pvtTbl.RowKeys”和“pvtTbl.ColumnKeys”
    //要获取行\列标题标签并使用“pvtTbl.GetValue()”
    //或“pvtTbl[]”以透视表获取值
    //您可以轻松地将透视表渲染为HTM
    
    var working =
        data
            .Select(d => new
            {
                Region_L1 = d[0],
                Region_L2 = d[1],
                Product_L1 = d[2],
                Product_L2 = d[3],
                Value = double.Parse(d[4]),
            });
    
    var output =
        working
            .GroupBy(x => new { x.Region_L1, x.Region_L2 }, x => new { x.Product_L1, x.Product_L2, x.Value })
            .Select(x => new { x.Key, Lookup = x.ToLookup(y => new { y.Product_L1, y.Product_L2 }, y => y.Value) })
            .Select(x => new
            {
                x.Key.Region_L1,
                x.Key.Region_L2,
                P_One_One = x.Lookup[new { Product_L1 = "Product One", Product_L2 = "Item One" }].Sum(),
                P_One_Two = x.Lookup[new { Product_L1 = "Product One", Product_L2 = "Item Two" }].Sum(),
                P_One_Four = x.Lookup[new { Product_L1 = "Product One", Product_L2 = "Item Four" }].Sum(),
                P_One_Six = x.Lookup[new { Product_L1 = "Product One", Product_L2 = "Item Six" }].Sum(),
                P_One_Eight = x.Lookup[new { Product_L1 = "Product One", Product_L2 = "Item Eight" }].Sum(),
                P_One_Eleven = x.Lookup[new { Product_L1 = "Product One", Product_L2 = "Item Eleven" }].Sum(),
                P_Two_Five = x.Lookup[new { Product_L1 = "Product Two", Product_L2 = "Item Five" }].Sum(),
                P_Two_Seven = x.Lookup[new { Product_L1 = "Product Two", Product_L2 = "Item Seven" }].Sum(),
                P_Two_Nine = x.Lookup[new { Product_L1 = "Product Two", Product_L2 = "Item Nine" }].Sum(),
                P_Three_Three = x.Lookup[new { Product_L1 = "Product Three", Product_L2 = "Item Three" }].Sum(),
                P_Three_Ten = x.Lookup[new { Product_L1 = "Product Three", Product_L2 = "Item Ten" }].Sum(),
            });
    
    var output =
        working
            .GroupBy(x => new { x.Region_L1, x.Region_L2 }, x => new { x.Product_L1, x.Product_L2, x.Value })
            .Select(x => new { x.Key, Lookup = x.ToLookup(y => new { y.Product_L1, y.Product_L2 }, y => y.Value) })
            .Select(x => new
            {
                x.Key.Region_L1,
                x.Key.Region_L2,
                Headers =
                    headerInfo
                        .Select(y => new { Product_L1 = y[0], Product_L2 = y[1] })
                        .Select(y => new { y.Product_L1, y.Product_L2, Value = x.Lookup[y].Sum() })
                        .ToArray(),
            });
    
    public class PivotData
    {
        public IReadOnlyList<PivotValues> Columns { get; set; }
        public IReadOnlyList<PivotDataRow> Rows { get; set; }
    }
    
    public class PivotDataRow
    {
        public PivotValues Data { get; set; }
        public IReadOnlyList<PivotValues> Values { get; set; }
    }
    
    public class PivotValues : IReadOnlyList<string>, IEquatable<PivotValues>, IComparable<PivotValues>
    {
        readonly IReadOnlyList<string> source;
        readonly int offset, count;
        public PivotValues(IReadOnlyList<string> source) : this(source, 0, source.Count) { }
        public PivotValues(IReadOnlyList<string> source, int offset, int count)
        {
            this.source = source;
            this.offset = offset;
            this.count = count;
        }
        public string this[int index] => source[offset + index];
        public int Count => count;
        public IEnumerator<string> GetEnumerator()
        {
            for (int i = 0; i < count; i++)
                yield return this[i];
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        public override int GetHashCode()
        {
            unchecked
            {
                var comparer = EqualityComparer<string>.Default;
                int hash = 17;
                for (int i = 0; i < count; i++)
                    hash = hash * 31 + comparer.GetHashCode(this[i]);
                return hash;
            }
        }
        public override bool Equals(object obj) => Equals(obj as PivotValues);
        public bool Equals(PivotValues other)
        {
            if (this == other) return true;
            if (other == null) return false;
            var comparer = EqualityComparer<string>.Default;
            for (int i = 0; i < count; i++)
                if (!comparer.Equals(this[i], other[i])) return false;
            return true;
        }
        public int CompareTo(PivotValues other)
        {
            if (this == other) return 0;
            if (other == null) return 1;
            var comparer = Comparer<string>.Default;
            for (int i = 0; i < count; i++)
            {
                var compare = comparer.Compare(this[i], other[i]);
                if (compare != 0) return compare;
            }
            return 0;
        }
        public override string ToString() => string.Join(", ", this); // For debugging
    }
    
    public static PivotData ToPivot(this List<List<string>> data, int rowDataCount, int columnDataCount, int valueDataCount)
    {
        int rowDataStart = 0;
        int columnDataStart = rowDataStart + rowDataCount;
        int valueDataStart = columnDataStart + columnDataCount;
    
        var columns = data
            .Select(r => new PivotValues(r, columnDataStart, columnDataCount))
            .Distinct()
            .OrderBy(c => c) // Optional
            .ToList();
    
        var emptyValues = new PivotValues(new string[valueDataCount]); // For missing (row, column) intersection
    
        var rows = data
            .GroupBy(r => new PivotValues(r, rowDataStart, rowDataCount))
            .Select(rg => new PivotDataRow
            {
                Data = rg.Key,
                Values = columns.GroupJoin(rg,
                    c => c,
                    r => new PivotValues(r, columnDataStart, columnDataCount),
                    (c, vg) => vg.Any() ? new PivotValues(vg.First(), valueDataStart, valueDataCount) : emptyValues
                ).ToList()
            })
            .OrderBy(r => r.Data) // Optional
            .ToList();
    
        return new PivotData { Columns = columns, Rows = rows };
    }
    
    var pivotData = data.ToPivot(2, 2, 1);
    var json = JsonConvert.SerializeObject(pivotData);
    var pivotData2 = JsonConvert.DeserializeObject<PivotData>(json);
    
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    
    namespace pivot
    {
        class Program
        {
            static void Main(string[] args)
            {
                var _staticColumnCount = 2; //Columns that should not be pivoted        
                var _dynamicColumnCount = 2; // Columns which needs to be pivoted to form header            
                var _valueColumnCount = 1; //Columns that represent Actual value        
                var valueColumnIndex = 4; //Assuming index starts with 0;
    
                List<List<string>> headerInfo = new List<List<string>>();
                headerInfo.Add(new List<string> { "Product Three", "Item Three" });
                headerInfo.Add(new List<string> { "Product Two", "Item Five" });
                headerInfo.Add(new List<string> { "Product Two", "Item Seven" });
                headerInfo.Add(new List<string> { "Product Two", "Item Nine" });
                headerInfo.Add(new List<string> { "Product One", "Item One" });
                headerInfo.Add(new List<string> { "Product One", "Item Two" });
                headerInfo.Add(new List<string> { "Product One", "Item Four" });
                headerInfo.Add(new List<string> { "Product One", "Item Six" });
                headerInfo.Add(new List<string> { "Product One", "Item Eight" });
                headerInfo.Add(new List<string> { "Product One", "Item Eleven" });
                headerInfo.Add(new List<string> { "Product Three", "Item Ten" });
    
    
                List<List<string>> data = new List<List<string>>();
                data.Add(new List<string> { "Global", "Europe", "Product One", "Item One", "579984.59" });
                data.Add(new List<string> { "Global", "North America", "Product One", "Item Two", "314586.73" });
                data.Add(new List<string> { "Global", "Asia", "Product One", "Item One", "62735.13" });
                data.Add(new List<string> { "Global", "Asia", "Product Two", "Item Five", "12619234.69" });
                data.Add(new List<string> { "Global", "North America", "Product Two", "Item Five", "8953713.39" });
                data.Add(new List<string> { "Global", "Europe", "Product One", "Item Two", "124267.4" });
                data.Add(new List<string> { "Global", "Asia", "Product One", "Item Four", "482338.49" });
                data.Add(new List<string> { "Global", "North America", "Product One", "Item Four", "809185.13" });
                data.Add(new List<string> { "Global", "Europe", "Product One", "Item Four", "233101" });
                data.Add(new List<string> { "Global", "Asia", "Product One", "Item Two", "120561.65" });
                data.Add(new List<string> { "Global", "North America", "Product One", "Item Six", "1517359.37" });
                data.Add(new List<string> { "Global", "Europe", "Product One", "Item Six", "382590.45" });
                data.Add(new List<string> { "Global", "North America", "Product One", "Item Eight", "661835.64" });
                data.Add(new List<string> { "Global", "Europe", "Product Three", "Item Three", "0" });
                data.Add(new List<string> { "Global", "Europe", "Product One", "Item Eight", "0" });
                data.Add(new List<string> { "Global", "Europe", "Product Two", "Item Five", "3478145.38" });
                data.Add(new List<string> { "Global", "Asia", "Product One", "Item Six", "0" });
                data.Add(new List<string> { "Global", "North America", "Product Two", "Item Seven", "4247059.97" });
                data.Add(new List<string> { "Global", "Asia", "Product Two", "Item Seven", "2163718.01" });
                data.Add(new List<string> { "Global", "Europe", "Product Two", "Item Seven", "2158782.48" });
                data.Add(new List<string> { "Global", "North America", "Product Two", "Item Nine", "72634.46" });
                data.Add(new List<string> { "Global", "Europe", "Product Two", "Item Nine", "127500" });
                data.Add(new List<string> { "Global", "North America", "Product One", "Item One", "110964.44" });
                data.Add(new List<string> { "Global", "Asia", "Product Three", "Item Ten", "2064.99" });
                data.Add(new List<string> { "Global", "Europe", "Product One", "Item Eleven", "0" });
                data.Add(new List<string> { "Global", "Asia", "Product Two", "Item Nine", "1250" });
    
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();    
                Reducer reducer = new Reducer();
                reducer.headerCount = headerInfo.Count;
    
                reducer.headerCount = headerInfo.Count;
                var resultCount = (int)Math.Ceiling((double)data.Count / (double)reducer.headerCount);
    
                ValueArray[,] results = new ValueArray[resultCount, _staticColumnCount + reducer.headerCount];
    
                reducer.headerDict = new Dictionary<IEnumerable<string>, int>(new MyComparer());
                reducer.skipCols = _staticColumnCount;
                reducer.headerKeys = _dynamicColumnCount;
                reducer.rowDict = new Dictionary<IEnumerable<string>, int>(new MyComparer());
                reducer.currentLine = 0;
                reducer.valueCount = _valueColumnCount;
                for (int i = 0; i < reducer.headerCount; i++)
                {
                    reducer.headerDict.Add(headerInfo[i], i);
                }
    
                results = data.Aggregate(results, reducer.reduce);
                stopwatch.Stop();
                Console.WriteLine("millisecs: " + stopwatch.ElapsedMilliseconds);
                for (int i = 0; i < resultCount; i++)
                {
                    var curr_header = new string[reducer.headerCount];
                    IEnumerable<string> curr_key = null;
                    for (int j = 0; j < reducer.headerCount; j++)
                    {
                        curr_header[j] = "[" +
                            String.Join(",", (results[i, reducer.skipCols + j]?.values) ?? new string[0])
                            + "]";
                        curr_key = curr_key ?? (results[i, reducer.skipCols + j]?.row_keys);
                    }
                    Console.WriteLine(String.Join(",", curr_key)
                        + ": " + String.Join(",", curr_header)
                        );
                }
                Console.ReadKey();
                // if you want to compare it to the accepted answer
                stopwatch.Reset();
                stopwatch.Start();
                var pivotData = data.ToPivot(2, 2, 1); // with all needed classes/methods
                stopwatch.Stop();
                Console.WriteLine("millisecs: " + stopwatch.ElapsedMilliseconds);
    
            Console.ReadKey();
            }
            internal class ValueArray
            {
                internal IEnumerable<string> row_keys;
                internal string[] values;
            }
    
            internal class Reducer
            {
                internal int headerCount;
                internal int skipCols;
                internal int headerKeys;
                internal int valueCount;
                internal Dictionary<IEnumerable<string>, int> headerDict;
                internal Dictionary<IEnumerable<string>, int> rowDict;
                internal int currentLine;
                internal ValueArray[,] reduce(ValueArray[,] results, List<string> line)
                {
                    var header_col = headerDict[line.Skip(skipCols).Take(headerKeys)];
                    var row_keys = line.Take(skipCols);
    
                    var curr_values = new string[valueCount];
                    for (int i = 0; i < valueCount; i++)
                    {
                        curr_values[i] = line[skipCols + headerKeys + i];
                    }
    
                    if (rowDict.ContainsKey(row_keys))
                    {
                        results[rowDict[row_keys], skipCols + header_col] = new ValueArray();
                        results[rowDict[row_keys], skipCols + header_col].row_keys = row_keys;
                        results[rowDict[row_keys], skipCols + header_col].values = curr_values;
                    }
                    else
                    {
                        rowDict.Add(row_keys, currentLine);
                        results[currentLine, skipCols + header_col] = new ValueArray();
                        results[currentLine, skipCols + header_col].row_keys = row_keys;
                        results[currentLine, skipCols + header_col].values = curr_values;
                        currentLine++;
                    }
                    return results;
                }
            }
    
            public class MyComparer : IEqualityComparer<IEnumerable<string>>
            {
                public bool Equals(IEnumerable<string> x, IEnumerable<string> y)
                {
                    return x.SequenceEqual(y);
                }
    
                public int GetHashCode(IEnumerable<string> obj)
                {
                    return obj.First().GetHashCode();
                }
            }
    
        }
    }