LINQ swap columns into rows

前端 未结 5 1834
死守一世寂寞
死守一世寂寞 2020-12-06 10:44

Is there a fancy LINQ expression that could allow me to do the following in a much more simpler fashion. I have a List>, assuming the L

相关标签:
5条回答
  • 2020-12-06 11:20

    Here's one that works for rectangular (non-ragged) matrices. The C# code here works cut-and-paste into LinqPad, a free, interactive C# programming tool.

    I define a postfix operator (that is, an extension method) "Transpose." Use the operator as follows:

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

    resulting in something like this:

    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
    

    The gist of the implementation of this operator is the following

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

    Here is the full implementation, with some lines commented out that you can uncomment to monitor how the function works.

    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")
                ;
        }
    }
    
    0 讨论(0)
  • 2020-12-06 11:25
    var inverted = Enumerable.Range(0, columnCount)
                   .Select(index => columnList.Select(list => list[index]));
    

    In short, we enumerate the column index from a range and use it to collect the nth element of each list.

    Please note that you'll need to check that every list has the same number of columns.

    0 讨论(0)
  • 2020-12-06 11:29

    Here's a Linq expression that would do what you want - looking at it I'd personally stick with the nested foreach loops though - much easier to read:

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

    This example also would only work on a matrix with a fixed column count. Basically it's flattening the matrix into a list, then creating the list of rows by grouping by the index of the element in the list modulo the column count.

    Edit:

    A different approach, much closer to a nested loop and probably similar performance besides the overhead.

    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();
    
    0 讨论(0)
  • 2020-12-06 11:40

    I am combining some of the answers above, which sometimes had columns and rows inverted form the original answer or from the convention I am used to : row refers to the first index and column to the inner ( second) index. e.g. values[row][column]

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

    Actually the word row and column is just our convention of thinking about the data in rows and columns , and sometimes adds more confusion than solving them.

    We are actually just swapping the inner index for the outer index. (or flipping the indexes around). So one could also just define the following extension method. . Again I borrowed from above solutions, just put it into something I find readable and fairly compact.

    Checks that the inner lists are of equal sized are required.

        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;
        }            
    
    0 讨论(0)
  • 2020-12-06 11:45

    You could LINQify the inner loop pretty easily:

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

    Whether or not that improves the readability is left entirely up to you!

    0 讨论(0)
提交回复
热议问题