【问题标题】:LINQ swap columns into rowsLINQ 将列交换为行
【发布时间】:2011-08-05 01:31:59
【问题描述】:

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

int columns = 5;
var values; // assume initialised as List<List<double>>()

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

【问题讨论】:

    标签: c# linq


    【解决方案1】:

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

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

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

    【讨论】:

    • @DBM: AddRange 的定义是什么?
    【解决方案2】:

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

    var columnList= new  List<List<double>>();
    columnList.Add(new List<double>() { 1, 2, 3 });
    columnList.Add(new List<double>() { 4, 5, 6 });
    columnList.Add(new List<double>() { 7, 8, 9 });
    columnList.Add(new List<double>() { 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=> x.V).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();
    

    【讨论】:

    • 嗯,可读性可能不太友好。我的版本和 Linq 表达式的性能是否相同?
    • @Seth:我认为它的性能worse 比您的版本,因为它必须展平然后重新分组,而 for 循环直接使用列表项的索引来创建行列表
    【解决方案3】:
    var inverted = Enumerable.Range(0, columnCount)
                   .Select(index => columnList.Select(list => list[index]));
    

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

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

    【讨论】:

      【解决方案4】:

      这是一种适用于矩形(非参差不齐)矩阵的方法。此处的 C# 代码可剪切并粘贴到免费的交互式 C# 编程工具 LinqPad

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

          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<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> xss)
          {
              var heads = xss.Heads();
              var tails = xss.Tails();
      
              var empt = new List<IEnumerable<T>>();
              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<T> Heads<T>(this IEnumerable<IEnumerable<T>> xss)
          {
              Debug.Assert(xss != null);
              if (xss.Any(xs => xs.IsEmpty()))
                  return new List<T>();
              return xss.Select(xs => xs.First());
          }
      
          public static bool IsEmpty<T>(this IEnumerable<T> xs)
          {
              return xs.Count() == 0;
          }
      
          public static IEnumerable<IEnumerable<T>> Tails<T>(this IEnumerable<IEnumerable<T>> xss)
          {
              return xss.Select(xs => xs.Skip(1));
          }
      
          public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> 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<IEnumerable<T>>();
              if (heads.IsEmpty())
                  return empt;
              empt.Add(heads);
              return empt.Concat(tails.Transpose())
      //          .Dump("empt")
                  ;
          }
      }
      

      【讨论】:

        【解决方案5】:

        我正在结合上面的一些答案,有时列和行与原始答案或我习惯的惯例相反:行指的是第一个索引,列指的是内部(第二个)索引。例如值[行][列]

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

        实际上,行和列这个词只是我们思考行和列中数据的惯例,有时比解决它们增加了更多的混乱。

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

        需要检查内部列表的大小是否相等。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-09-27
          • 2022-01-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多