【问题标题】:Rotate - Transposing a List<List<string>> using LINQ C#旋转 - 使用 LINQ C# 转置 List<List<string>>
【发布时间】:2016-09-14 07:41:07
【问题描述】:

我有一个List&lt;List&lt;string&gt;&gt;,它是从远程数据源(即 WCF)返回的。所以,我需要使用 LINQ 将以下数据修改为用户友好的列表

C#代码是

List<List<string>> PersonInfo = new List<List<string>>()
{
    new List<string>() {"John", "Peter", "Watson"},
    new List<string>() {"1000", "1001", "1002"}
}

适当的屏幕截图:现有

我需要像下面的截图一样旋转数据:提议

请帮助我如何使用 LINQ C#

旋转数据

【问题讨论】:

  • 主列表中是否总是有两个列表?
  • @vmutafov 是的。 List&lt;List&lt;string&gt;&gt;
  • 问题是:PersonInfo 是否总是包含 2 个列表,还是可以超过 2 个?
  • 我不确定你是否理解我的意思。我的意思是,是否总是会有一个包含名称的列表和一个包含数字的列表?
  • 这叫做transposing,而不是pivoting

标签: c# linq pivot-table transpose


【解决方案1】:

这是一个简单而灵活的解决方案,它将处理具有任意数量维度的多个内部列表。

List<List<string>> PersonInfo = new List<List<string>>()
{
    new List<string>() {"John", "Peter", "Watson"},
    new List<string>() {"1000", "1001", "1002"}
};


var result = PersonInfo
    .SelectMany(inner => inner.Select((item, index) => new { item, index }))
    .GroupBy(i => i.index, i => i.item)
    .Select(g => g.ToList())
    .ToList();

【讨论】:

  • 我意识到这可能会恢复这个线程,但我并不完全熟悉 VB.net 的这一部分,我想知道如果输入列表是,是否可以插入 Nothing 或类似的占位符值不均匀?
  • @pmackni 这完全有可能。不过,我想说这值得一个单独的问题 - 如何将锯齿状数组转换为矩形数组,用特定值填充间隙。
  • 很惊讶这行得通(确实如此)!它在很大程度上取决于GroupBy 的实现,因为它保证保留组的order 以及组内元素的顺序!也就是说,我认为您可以省略 to .ToList() 调用,因为 IEnumerables 是 arrays under the hood
  • @3dGrabber .ToList() 调用是为了满足问题中指定的类型。转置不需要它们,仅此而已,您可以完全丢失最后两行。
【解决方案2】:

这是一个通用的扩展方法

public static IEnumerable<IEnumerable<T>> Pivot<T>(this IEnumerable<IEnumerable<T>> source)
{
    var enumerators = source.Select(e => e.GetEnumerator()).ToArray();
    try
    {
        while (enumerators.All(e => e.MoveNext()))
        {
            yield return enumerators.Select(e => e.Current).ToArray();
        }
    }
    finally
    {
        Array.ForEach(enumerators, e => e.Dispose());
    }
}

所以你可以

var result = PersonInfo.Pivot();

【讨论】:

    【解决方案3】:

    假设PersonInfo 中只有 2 个列表:

    var rotated = PersonInfo[0]
        .Zip(PersonInfo[1], (a, b) => new List<string> { a, b }).ToList();
    

    如果 PersonInfo 内部可以有任意数量的 List:

    Enumerable.Range(0, PersonInfo[0].Count)
        .Select(i => PersonInfo.Select(lst => lst[i]).ToList()).ToList();
    

    【讨论】:

      【解决方案4】:

      您可以使用Enumerable.RangeEnumerable.ElementAtOrDefault

      List<List<string>> rotated = Enumerable.Range(0, PersonInfo.Max(list => list.Count))
       .Select(i => PersonInfo.Select(list => list.ElementAtOrDefault(i)).ToList())
       .ToList();
      

      PersonInfo.Max(list =&gt; list.Count) 返回列表的最大大小。这将是主列表的新大小,在本例中为 3。Enumerable.Range 就像一个 for 循环。对于每个列表,它现在将选择这些索引处的所有字符串。如果大小不同,您将获得null(因为ElementAtOrDefault)。

      如果列表的大小相同,您可以应用相同的查询来取回原始列表:

      PersonInfo = Enumerable.Range(0, rotated.Max(list => list.Count))
       .Select(i => rotated.Select(list => list.ElementAtOrDefault(i)).ToList())
       .ToList();
      

      作为扩展:

      public static IEnumerable<IList<T>> Rotate<T>(this IEnumerable<IList<T>> sequences)
      {
          var list = sequences as IList<IList<T>> ?? sequences.ToList();
          int maxCount = list.Max(l => l.Count);
          return Enumerable.Range(0, maxCount)
              .Select(i => list.Select(l => l.ElementAtOrDefault(i)).ToList());
      }
      

      用法:

      IEnumerable<IList<string>> rotated = PersonInfo.Rotate();
      IEnumerable<IList<string>> rotatedPersonInfo = rotated.Rotate(); // append ToList to get the original list
      

      【讨论】:

      • @Oliver:为什么要删除?当然,列表必须具有相同的大小,但这里就是这种情况。但是感谢您的注意,我已经修复了我的答案
      • 我把它读作Min 而不是Max (duh) 所以我指的是数据丢失而不是附加值。不一样的!
      【解决方案5】:

      这将上面的 Zip 理念扩展到任意数量的列表。 Zip 会将行列表截断到最小的等级。

      List<List<string>> PersonInfo = new List<List<string>>()
      {
          new List<string>() {"John", "Peter", "Watson"},
          new List<string>() {"1000", "1001", "1002"},
          new List<string>() {"2000", "2001", "2002"},
          new List<string>() {"3000", "3001", "3002"}
      };
      
      var seed = Enumerable.Empty<List<string>>();
      var transformed = PersonInfo.Aggregate(seed, (acc, r) =>
         acc.Any()
       ? acc.Zip(r, (row, nextElement) => { row.Add(nextElement); return row; })
       : r.Select(e => new List<string> { e }) //initialize target list using first row
      ); 
      

      【讨论】:

        【解决方案6】:

        只需执行以下操作:

        var persons = Enumerable.Range(0, PersonInfo.First().Count()).Select(i => PersonInfo.Select(e => e[i]).ToList()).ToList();
        

        var persons = Enumerable.Range(0, PersonInfo[0].Count()).Select(i => {
            return PersonInfo.Select(e => {
                return e[i];
            }).ToList();
        }).ToList();
        

        并检查如下结果:

        persons.ForEach(p => Console.WriteLine("{0} {1}", p[0], p[1]));
        

        【讨论】:

          【解决方案7】:

          试试这个:

          List<List<string>> PersonInfo = new List<List<string>>(){
          new List<string>() {"John", "Peter", "Watson"},
          new List<string>() {"1000", "1001", "1002"}};
          
          List<List<string>> PivitedPersonInfo = new List<List<string>>();
          for (int i = 0; i < PersonInfo.First().Count; i++)
          {
              PivitedPersonInfo.Add(PersonInfo.Select(x => x.ElementAt(i)).ToList());
          }
          

          【讨论】:

            猜你喜欢
            • 2012-08-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-06-25
            • 1970-01-01
            • 2012-12-30
            • 2011-06-27
            • 2021-09-17
            相关资源
            最近更新 更多