旋转 - 使用 LINQ C 转置 List<List<string>>#

Rotate - Transposing a List<List<string>> using LINQ C#

提问人:B.Balamanigandan 提问时间:9/14/2016 最后编辑:B.Balamanigandan 更新时间:6/6/2021 访问量:8769

问:

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

C# 代码是

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

适当的屏幕截图:现有

enter image description here

我需要旋转数据,如下图所示: 建议

enter image description here

请帮助我如何使用 LINQ C# 旋转数据

C# LINQ 数据透视表 转置

评论

0赞 vmutafov 9/14/2016
主列表内总是有两个列表吗?
0赞 B.Balamanigandan 9/14/2016
@vmutafov 是的。List<List<string>>
0赞 Dennis_E 9/14/2016
问题是:PersonInfo 是否总是包含 2 个列表,或者可以有 2 个以上?
0赞 vmutafov 9/14/2016
我不确定你是否理解我。我的意思是,总会有一个带有名字的列表和一个带有数字的列表吗?
2赞 Good Night Nerd Pride 9/14/2016
顺便说一句,这称为转置而不是旋转

答:

-1赞 sachin 9/14/2016 #1

试试这个:

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());
}
2赞 Dennis_E 9/14/2016 #2

假设里面只有 2 个列表:PersonInfo

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

如果 PersonInfo 中可以有任意数量的列表:

Enumerable.Range(0, PersonInfo[0].Count)
    .Select(i => PersonInfo.Select(lst => lst[i]).ToList()).ToList();
10赞 fubo 9/14/2016 #3

下面是一个通用的扩展方法

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();
32赞 Oliver 9/14/2016 #4

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

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();

评论

0赞 pmackni 2/12/2020
我意识到这可能会恢复这个线程,但是由于并不完全熟悉这一段 VB.net,我想知道如果输入列表不均匀,是否可以插入 Nothing 或类似的占位符值?
3赞 Oliver 2/12/2020
@pmackni 这是完全可能的。不过,我想说的是,这是一个值得单独讨论的问题——如何将锯齿状数组转换为矩形数组,用特定值填充空白。
0赞 3dGrabber 3/13/2022
惊讶于这有效(它确实如此)!它很大程度上取决于 的实现,从某种意义上说,它保证了组的顺序以及组内元素的顺序!也就是说,我认为您可以省略 to 调用,因为 s 是引擎盖下的数组GroupBy.ToList()IEnumerable
1赞 Oliver 3/14/2022
@3dGrabber 调用是为了满足问题中指定的类型。转置不需要它们,仅此而已,您就可以完全丢失最后两行。.ToList()
2赞 Tim Schmelter 9/14/2016 #5

您可以使用和: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 => list.Count)返回列表的 max-size。这将是主列表的新大小,在本例中为 3。 就像一个 for 循环。对于每个列表,它现在将选择这些索引处的所有字符串。如果大小不同,您将得到(因为 )。Enumerable.RangenullElementAtOrDefault

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

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

评论

0赞 Tim Schmelter 9/14/2016
@Oliver:为什么要删除?当然,列表必须具有相同的大小,但这里就是这种情况。但感谢您的注意,我已经解决了我的答案
0赞 Oliver 9/14/2016
我把它读作不是(呃),所以我指的是数据丢失而不是附加值。与众不同!MinMax
0赞 andredking 7/23/2018 #6

这将上面的 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
); 
0赞 Masaki Ohashi 6/6/2021 #7

只需执行以下操作:

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]));