LINQ将列交换为行

是否有一个花哨的LINQ表达式,可以让我以更简单的方式执行以下操作。 我有一个List<List> ,假设List是2d矩阵中的列,我想将列列表交换为行列表。 我有以下明显的解决方案:

 int columns = 5; var values; // assume initialised as List<List>() var listOfRows = new List<List>(); for (int i = 0; i < columns ; i++) { List newRow = new List(); foreach (List value in values) { newRow.Add(value[i]); } listOfRows.Add(newRow); } 

你可以很容易地LINQify内部循环:

vector.AddRange(values.Select(value => value[i]));

是否提高可读性完全取决于您!

这是一个Linq表达式可以做你想要的 – 看着它我个人坚持使用嵌套的foreach循环 – 更容易阅读:

 var columnList= new List>(); columnList.Add(new List() { 1, 2, 3 }); columnList.Add(new List() { 4, 5, 6 }); columnList.Add(new List() { 7, 8, 9 }); columnList.Add(new List() { 10, 11, 12 }); int columnCount = columnList[0].Count; var rowList = columnList.SelectMany(x => x) .Select((x, i) => new { V = x, Index = i }) .GroupBy(x => (x.Index + 1) % columnCount) .Select(g => g.Select( x=> xV).ToList()) .ToList(); 

此示例也仅适用于具有固定列数的矩阵。 基本上它将矩阵展平为一个列表,然后通过按列列表中元素的索引进行分组来创建行列表。

编辑:

一种不同的方法,更接近嵌套循环,除了开销之外可能还有类似的性能。

 int columnCount = columnList[0].Count; int rowCount = columnList.Count; var rowList = Enumerable.Range(0, columnCount) .Select( x => Enumerable.Range(0, rowCount) .Select(y => columnList[y][x]) .ToList()) .ToList(); 
 var inverted = Enumerable.Range(0, columnCount) .Select(index => columnList.Select(list => list[index])); 

简而言之,我们从一个范围枚举列索引并使用它来收集每个列表的第n个元素。

请注意,您需要检查每个列表是否具有相同的列数。

这是适用于矩形(非粗糙)矩阵的一种。 这里的C#代码可以剪切并粘贴到LinqPad中 ,这是一个免费的交互式C#编程工具。

我定义了一个后缀运算符(即扩展方法)“Transpose”。 使用运算符如下:

  var rand = new Random(); var xss = new [] { new [] {rand.NextDouble(), rand.NextDouble()}, new [] {rand.NextDouble(), rand.NextDouble()}, new [] {rand.NextDouble(), rand.NextDouble()}, }; xss.Dump("Original"); xss.Transpose().Dump("Transpose"); 

导致这样的事情:

 Original 0.843094345109116 0.981432441613373 0.649207864724662 0.00594645645746331 0.378864820291691 0.336915332515219 Transpose 0.843094345109116 0.649207864724662 0.378864820291691 0.981432441613373 0.00594645645746331 0.336915332515219 

该运算符的实现要点如下

  public static IEnumerable> Transpose(this IEnumerable> xss) { var heads = xss.Heads(); var tails = xss.Tails(); var empt = new List>(); if (heads.IsEmpty()) return empt; empt.Add(heads); return empt.Concat(tails.Transpose()); } 

这是完整的实现,有些行注释掉您可以取消注释以监视函数的工作方式。

 void Main() { var rand = new Random(); var xss = new [] { new [] {rand.NextDouble(), rand.NextDouble()}, new [] {rand.NextDouble(), rand.NextDouble()}, new [] {rand.NextDouble(), rand.NextDouble()}, }; xss.Dump("Original"); xss.Transpose().Dump("Transpose"); } public static class Extensions { public static IEnumerable Heads(this IEnumerable> xss) { Debug.Assert(xss != null); if (xss.Any(xs => xs.IsEmpty())) return new List(); return xss.Select(xs => xs.First()); } public static bool IsEmpty(this IEnumerable xs) { return xs.Count() == 0; } public static IEnumerable> Tails(this IEnumerable> xss) { return xss.Select(xs => xs.Skip(1)); } public static IEnumerable> Transpose(this IEnumerable> xss) { // xss.Dump("xss in Transpose"); var heads = xss.Heads() // .Dump("heads in Transpose") ; var tails = xss.Tails() // .Dump("tails in Transpose") ; var empt = new List>(); if (heads.IsEmpty()) return empt; empt.Add(heads); return empt.Concat(tails.Transpose()) // .Dump("empt") ; } } 

我正在结合上面的一些答案,它们有时会从原始答案或我习惯的惯例中反转出列和行:行指的是内部(第二个)索引的第一个索引和列。 例如值[row] [column]

  public static List> Transpose(this List> values) { if (values.Count == 0 || values[0].Count == 0) { return new List>(); } int ColumnCount = values[0].Count; var listByColumns = new List>(); foreach (int columnIndex in Enumerable.Range(0, ColumnCount)) { List valuesByColumn = values.Select(value => value[columnIndex]).ToList(); listByColumns.Add(valuesByColumn); } return listByColumns; } 

实际上,行和列这个词只是我们考虑行和列中数据的惯例,有时会比解决它们更加困惑。

我们实际上只是交换外部索引的内部索引。 (或翻转指数)。 因此,也可以定义以下扩展方法。 。 我再次从上面的解决方案借鉴,只是将它放入我认为可读且相当紧凑的东西中。

检查内部列表的大小是否相同。

  public static List> InsideOutFlip(this List> values) { if (values.Count == 0 || values[0].Count == 0) { return new List>(); } int innerCount = values[0].Count; var flippedList = new List>(); foreach (int innerIndex in Enumerable.Range(0, innerCount)) { List valuesByOneInner = values.Select(value => value[innerIndex]).ToList(); flippedList.Add(valuesByOneInner); } return flippedList; }