Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm having a List<List<string>>, which is return from the remote data source (i.e., WCF). So, I need to modify the following data into a user-friendly list using LINQ

The C# Code is

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

Appropriate Screen Shot: Existing

enter image description here

I need to rotate the data as like the below Screenshot: Proposed

enter image description here

Kindly assist me how to rotate the data using LINQ C#

like image 514
B.Balamanigandan Avatar asked Sep 14 '16 07:09

B.Balamanigandan


4 Answers

This is a simple and flexible solution, it will handle multiple inner lists with any number of dimensions.

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();
like image 138
Oliver Avatar answered Nov 15 '22 15:11

Oliver


Here is a generic extension method

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

so you can

var result = PersonInfo.Pivot();
like image 34
fubo Avatar answered Nov 15 '22 15:11

fubo


Assuming there are only ever 2 lists inside PersonInfo:

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

If there can be any number of Lists inside of PersonInfo:

Enumerable.Range(0, PersonInfo[0].Count)
    .Select(i => PersonInfo.Select(lst => lst[i]).ToList()).ToList();
like image 26
Dennis_E Avatar answered Nov 15 '22 16:11

Dennis_E


You can use Enumerable.Range and Enumerable.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) returns the max-size of the lists. This will be the new size of the main list, in this case 3. Enumerable.Range is like a for-loop. For every list it will now select all strings at these indexes. If the sizes are different you'll get null(because of ElementAtOrDefault).

If the lists had the same size you can apply the same query to get the original list back:

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

As extension:

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

Usage:

IEnumerable<IList<string>> rotated = PersonInfo.Rotate();
IEnumerable<IList<string>> rotatedPersonInfo = rotated.Rotate(); // append ToList to get the original list
like image 39
Tim Schmelter Avatar answered Nov 15 '22 17:11

Tim Schmelter